2888 lines
77 KiB
C++
2888 lines
77 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"
|
|
#include <frame.hpp>
|
|
#include <xref.hpp>
|
|
#include <jumptable.hpp>
|
|
#include <segregs.hpp>
|
|
|
|
//#define DEBUG_ARGLOC
|
|
|
|
//----------------------------------------------------------------------
|
|
#ifdef DEBUG_ARGLOC
|
|
static void debug_print_argloc(int i, const argloc_t &argloc, const tinfo_t &type)
|
|
{
|
|
qstring typestr;
|
|
type.print(&typestr);
|
|
char varstr[MAXSTR];
|
|
if ( argloc.is_stkoff() )
|
|
qsnprintf(varstr, sizeof(varstr), "STK_%x", int(argloc.stkoff()));
|
|
else
|
|
print_argloc(varstr, sizeof(varstr), argloc, type.get_size());
|
|
if ( i == -1 )
|
|
msg("RET: %s %s\n", typestr.c_str(), varstr);
|
|
else
|
|
msg("%d: %s %s\n", i, typestr.c_str(), varstr);
|
|
}
|
|
#else
|
|
inline void debug_print_argloc(int, const argloc_t &, const tinfo_t &) {}
|
|
#endif
|
|
|
|
// does the expression [reg, xxx] point to the stack?
|
|
static bool is_stkptr(const insn_t &insn, int reg)
|
|
{
|
|
if ( reg == SP )
|
|
return true;
|
|
if ( reg == FP )
|
|
{
|
|
func_t *pfn = get_func(insn.ea);
|
|
|
|
if ( pfn != NULL && (pfn->flags & FUNC_FRAME) != 0 )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void arc_t::handle_operand(const insn_t &insn, const op_t & x, bool loading)
|
|
{
|
|
flags_t F;
|
|
switch ( x.type )
|
|
{
|
|
case o_reg:
|
|
break;
|
|
case o_imm:
|
|
set_immd(insn.ea);
|
|
F = get_flags(insn.ea);
|
|
if ( op_adds_xrefs(F, x.n) )
|
|
{
|
|
insn.add_off_drefs(x, dr_O, OOFS_IFSIGN);
|
|
}
|
|
else if ( x.n == 2 && may_create_stkvars() && !is_defarg(F, x.n)
|
|
&& insn.itype == ARC_add && !insn.Op1.is_reg(SP) && !insn.Op1.is_reg(FP)
|
|
&& (insn.Op2.is_reg(SP) || insn.Op2.is_reg(FP)) )
|
|
{
|
|
// add rx, sp, #imm
|
|
func_t *pfn = get_func(insn.ea);
|
|
if ( pfn != NULL )
|
|
{
|
|
adiff_t sp_off = x.value;
|
|
if ( insn.create_stkvar(x, sp_off, 0) )
|
|
op_stkvar(insn.ea, x.n);
|
|
}
|
|
}
|
|
break;
|
|
case o_mem:
|
|
if ( insn.itype != ARC_lr && insn.itype != ARC_sr )
|
|
{
|
|
ea_t ea = to_ea(insn.cs, x.addr);
|
|
insn.create_op_data(ea, x); // create the data item of the correct size
|
|
insn.add_dref(ea, x.offb, loading ? dr_R : dr_W);
|
|
if ( (idpflags & ARC_INLINECONST) != 0 && insn.itype == ARC_ld )
|
|
copy_insn_optype(insn, x, ea);
|
|
}
|
|
break;
|
|
case o_near:
|
|
{
|
|
int iscall = has_insn_feature(insn.itype, CF_CALL);
|
|
|
|
insn.add_cref(to_ea(insn.cs, x.addr), x.offb, iscall ? fl_CN : fl_JN);
|
|
if ( !islast && iscall )
|
|
{
|
|
if ( !func_does_return(x.addr) ) // delay slot?!
|
|
islast = 1;
|
|
}
|
|
}
|
|
break;
|
|
case o_displ:
|
|
set_immd(insn.ea);
|
|
F = get_flags(insn.ea);
|
|
if ( !is_defarg(F, x.n) )
|
|
{
|
|
ea_t base = BADADDR;
|
|
if ( x.reg == PCL )
|
|
base = insn.ea & (~3ul);
|
|
else if ( x.reg == NEXT_PC )
|
|
base = insn.ea + insn.size;
|
|
int sreg = get_base_sreg(x.reg);
|
|
if ( sreg > 0 )
|
|
base = get_sreg(insn.ea, sreg);
|
|
if ( base != BADADDR )
|
|
{
|
|
int scale = get_scale_factor(insn);
|
|
reftype_t reftype = REF_OFF32;
|
|
if ( scale == 2 )
|
|
reftype = ref_arcsoh_id | REFINFO_CUSTOM;
|
|
else if ( scale == 4 )
|
|
reftype = ref_arcsol_id | REFINFO_CUSTOM;
|
|
op_offset(insn.ea, x.n, reftype | REFINFO_NOBASE, BADADDR, base);
|
|
}
|
|
}
|
|
if ( op_adds_xrefs(F, x.n) ) // create an xref for offset expressions
|
|
{
|
|
if ( insn.itype == ARC_jli )
|
|
{
|
|
// for jli the reference target is called, not read
|
|
ea_t base = get_sreg(insn.ea, JLI_BASE);
|
|
insn.add_cref(base + 4 * x.addr, 0, fl_CF);
|
|
}
|
|
else if ( insn.itype == ARC_bi || insn.itype == ARC_bih )
|
|
{
|
|
// for bi/bih the reference target is jumped to, not read
|
|
ea_t next_pc = insn.ea + insn.size;
|
|
int scale = insn.itype == ARC_bi ? 4 : 2;
|
|
insn.add_cref(next_pc + scale * x.addr, 0, fl_JN);
|
|
}
|
|
else
|
|
{
|
|
ea_t target = insn.add_off_drefs(x, loading ? dr_R : dr_W, OOF_ADDR|OOF_SIGNED|OOFW_32);
|
|
if ( target != BADADDR )
|
|
insn.create_op_data(target, x); // create the data item of the correct size
|
|
}
|
|
}
|
|
else if ( is_stkptr(insn, x.phrase) && may_create_stkvars() && !is_defarg(F, x.n) )
|
|
{
|
|
func_t *pfn = get_func(insn.ea);
|
|
if ( pfn != NULL )
|
|
{
|
|
// if it's [sp, xxx] we make a stackvar out of it
|
|
adiff_t sp_off = x.addr;
|
|
if ( insn.create_stkvar(x, sp_off, STKVAR_VALID_SIZE) )
|
|
op_stkvar(insn.ea, x.n);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
inline bool is_callee_saved(int reg)
|
|
{
|
|
return reg >= ARC_ABI_FIRST_CALLEE_SAVED_REGISTER
|
|
&& reg <= ARC_ABI_LAST_CALLEE_SAVED_REGISTER;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Is register 'reg' spoiled by the current instruction?
|
|
#define PROC_MAXCHGOP 2
|
|
bool arc_t::spoils(const insn_t &insn, int reg) const
|
|
{
|
|
switch ( insn.itype )
|
|
{
|
|
case ARC_pop:
|
|
case ARC_push:
|
|
if ( reg == SP )
|
|
return true;
|
|
break;// otherwise check flags
|
|
|
|
case ARC_ld: // ld Rx, [reg, #imm]
|
|
case ARC_st: // st.a R1, [R2, #imm]
|
|
if ( insn.Op2.reg == reg && ((insn.auxpref & aux_amask) == aux_a || (insn.auxpref & aux_amask) == aux_ab) )
|
|
return true;
|
|
break;// otherwise check flags
|
|
|
|
case ARC_bl:
|
|
case ARC_jl:
|
|
return !is_callee_saved(reg);
|
|
|
|
case ARC_enter:
|
|
case ARC_leave:
|
|
{
|
|
if ( insn.Op1.reglist == 0 )
|
|
return false;
|
|
if ( reg == SP )
|
|
return true;
|
|
// FP is set to SP on enter and restored on leave
|
|
if ( (insn.Op1.reglist & REGLIST_FP) != 0 && reg == FP )
|
|
return true;
|
|
if ( insn.itype == ARC_enter )
|
|
return false;
|
|
int regs = insn.Op1.reglist & REGLIST_REGS;
|
|
return (insn.Op1.reglist & REGLIST_BLINK) != 0 && reg == BLINK
|
|
|| reg >= R13 && reg < R13 + regs;
|
|
}
|
|
}
|
|
|
|
uint32 feature = insn.get_canon_feature(ph);
|
|
if ( feature != 0 )
|
|
{
|
|
for ( int i = 0; i < PROC_MAXOP; ++i )
|
|
{
|
|
if ( has_cf_chg(feature, i) && insn.ops[i].is_reg(reg) )
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// does the instruction spoil the flags?
|
|
static bool spoils_flags(const insn_t &insn)
|
|
{
|
|
return insn.itype == ARC_cmp
|
|
|| insn.itype == ARC_flag
|
|
|| insn.itype == ARC_modif
|
|
|| insn.itype == ARC_fscmp
|
|
|| insn.itype == ARC_fscmpf
|
|
|| (insn.auxpref & aux_f) != 0;
|
|
}
|
|
|
|
// info about a single register
|
|
struct ldr_value_info_t
|
|
{
|
|
uval_t value; // value loaded into the register
|
|
ea_t val_ea; // where the value comes from (for constant pool or immediate loads)
|
|
eavec_t insn_eas; // insns that were involved in calculating the value
|
|
char n; // operand number
|
|
char state;
|
|
#define LVI_STATE 0x03 // state mask
|
|
#define LVI_UNKNOWN 0x00 // unknown state
|
|
#define LVI_VALID 0x01 // value known to be valid
|
|
#define LVI_INVALID 0x02 // value known to be invalid
|
|
#define LVI_CONST 0x04 // is the value constant? (e.g. immediate or const pool)
|
|
|
|
ldr_value_info_t(void)
|
|
: value(0), val_ea(BADADDR), n(0), state(LVI_UNKNOWN)
|
|
{}
|
|
bool is_const(void) const { return (state & LVI_CONST) != 0; }
|
|
bool is_valid(void) const { return (state & LVI_STATE) == LVI_VALID; }
|
|
bool is_known(void) const { return (state & LVI_STATE) != LVI_UNKNOWN; }
|
|
void set_valid(bool valid)
|
|
{
|
|
state &= ~LVI_STATE;
|
|
state |= valid ? LVI_VALID : LVI_INVALID;
|
|
}
|
|
void set_const(void) { state |= LVI_CONST; }
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
// helper class for find_op_value/find_ldr_value
|
|
// we keep a cache of discovered register values to avoid unnecessary recursion
|
|
struct reg_tracker_t
|
|
{
|
|
// map cannot store an array directly, so wrap it in a class
|
|
struct reg_values_t
|
|
{
|
|
ldr_value_info_t regs[R60+1]; // values for registers R0 to R60 for a specific ea
|
|
};
|
|
|
|
typedef std::map<ea_t, reg_values_t> reg_values_cache_t;
|
|
|
|
arc_t ±
|
|
// we save both valid and invalid values into in the cache.
|
|
reg_values_cache_t regcache;
|
|
|
|
reg_tracker_t(arc_t *p) : pm(*p) {}
|
|
// recursive functions; they can call each other, so we limit the nesting level
|
|
bool do_find_op_value(const insn_t &insn, const op_t &x, ldr_value_info_t *lvi, int nest_level);
|
|
bool do_find_ldr_value(const insn_t &insn, ea_t ea, int reg, ldr_value_info_t *p_lvi, int nest_level);
|
|
bool do_calc_complex_value(const insn_t &insn, const op_t &x, ldr_value_info_t *lvi, int nest_level);
|
|
|
|
bool is_call_insn(const insn_t &insn) const;
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
bool reg_tracker_t::is_call_insn(const insn_t &insn) const
|
|
{
|
|
switch ( insn.itype )
|
|
{
|
|
case ARC_bl:
|
|
case ARC_jli:
|
|
return true;
|
|
|
|
case ARC_jl:
|
|
if ( insn.Op1.reg != BLINK && insn.Op1.reg != ILINK1 && insn.Op1.reg != ILINK2 )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
bool arc_t::is_arc_call_insn(const insn_t &insn)
|
|
{
|
|
reg_tracker_t tr(this);
|
|
return tr.is_call_insn(insn);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
bool reg_tracker_t::do_find_op_value(const insn_t &insn, const op_t &x, ldr_value_info_t *lvi, int nest_level)
|
|
{
|
|
switch ( x.type )
|
|
{
|
|
case o_reg:
|
|
return do_find_ldr_value(insn, insn.ea, x.reg, lvi, nest_level);
|
|
case o_imm:
|
|
if ( lvi != NULL )
|
|
{
|
|
lvi->value = x.value & 0xFFFFFFFF;
|
|
lvi->set_const();
|
|
lvi->set_valid(true);
|
|
lvi->insn_eas.push_back(insn.ea);
|
|
}
|
|
return true;
|
|
case o_displ:
|
|
case o_phrase:
|
|
{
|
|
ldr_value_info_t val2;
|
|
if ( do_calc_complex_value(insn, x, &val2, nest_level+1) && val2.is_valid() )
|
|
{
|
|
if ( lvi != NULL )
|
|
{
|
|
*lvi = val2;
|
|
if ( lvi->is_valid() )
|
|
lvi->insn_eas.push_back(insn.ea);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
case o_mem:
|
|
if ( lvi != NULL )
|
|
{
|
|
ea_t value = to_ea(insn.cs, x.addr);
|
|
ea_t val_ea = BADADDR;
|
|
if ( insn.itype == ARC_ld && insn.Op2.dtype == dt_dword )
|
|
{
|
|
val_ea = value;
|
|
value = BADADDR;
|
|
if ( is_loaded(val_ea) )
|
|
{
|
|
value = get_dword(val_ea);
|
|
lvi->set_const();
|
|
lvi->set_valid(true);
|
|
lvi->insn_eas.push_back(insn.ea);
|
|
}
|
|
}
|
|
lvi->val_ea = uint32(val_ea);
|
|
lvi->value = uint32(value);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// check if ea is in a const segment, and so we can use the pointer value
|
|
static bool is_const_seg(ea_t ea)
|
|
{
|
|
if ( !is_loaded(ea) )
|
|
return false;
|
|
|
|
const char *const *names = NULL;
|
|
int ncnt = 0;
|
|
if ( inf_get_filetype() == f_MACHO )
|
|
{
|
|
static const char *const macho_segs[] =
|
|
{
|
|
"__const", "__const_coal",
|
|
"__text", "__dyld",
|
|
"__la_symbol_ptr", "__nl_symbol_ptr",
|
|
"__class", "__cls_refs", "__message_refs",
|
|
"__inst_meth", "__cat_inst_meth", "__cat_cls_meth",
|
|
"__constructor", "__destructor", "__pointers",
|
|
"__objc_protorefs",
|
|
"__objc_selrefs",
|
|
"__objc_classrefs",
|
|
"__objc_superrefs",
|
|
"__objc_const",
|
|
};
|
|
names = macho_segs;
|
|
ncnt = qnumber(macho_segs);
|
|
}
|
|
else if ( inf_get_filetype() == f_ELF )
|
|
{
|
|
static const char *const elf_segs[] =
|
|
{
|
|
".got", ".text", ".rodata",
|
|
".got.plt", ".plt",
|
|
".init", ".fini"
|
|
};
|
|
names = elf_segs;
|
|
ncnt = qnumber(elf_segs);
|
|
}
|
|
if ( names != NULL )
|
|
{
|
|
segment_t *seg = getseg(ea);
|
|
if ( seg != NULL )
|
|
{
|
|
qstring segname;
|
|
if ( get_segm_name(&segname, seg) > 0 )
|
|
{
|
|
for ( size_t i = 0; i < ncnt; i++ )
|
|
if ( segname == names[i] )
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( segtype(ea) == SEG_CODE )
|
|
return true;
|
|
|
|
segment_t *seg = getseg(ea);
|
|
if ( seg != NULL && (seg->perm & (SEGPERM_WRITE|SEGPERM_READ)) == SEGPERM_READ )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// calculate value of a complex operand
|
|
// ld [<Rn>, #+/-<offset>]
|
|
// ld [<Rn>, <Rm>]
|
|
// ld.a [<Rn>, #+/-<offset>]
|
|
// ld.ab [<Rn>, #+/-<offset>] (post-increment)
|
|
// val_ea is always calculated, val only for dword accesses to const segments
|
|
// returns true is val_ea is ok; value may be still wrong! set is_valid() for the value
|
|
bool reg_tracker_t::do_calc_complex_value(const insn_t &insn, const op_t &x, ldr_value_info_t *lvi, int nest_level)
|
|
{
|
|
ldr_value_info_t val1;
|
|
ea_t val_ea = BADADDR;
|
|
uval_t value = BADADDR;
|
|
bool ok = false;
|
|
if ( do_find_ldr_value(insn, insn.ea, x.reg, &val1, nest_level+1) )
|
|
{
|
|
ldr_value_info_t val2;
|
|
if ( (insn.auxpref & aux_amask) == aux_ab ) // post-increment
|
|
{
|
|
ok = true;
|
|
val2.value = 0;
|
|
}
|
|
else
|
|
{
|
|
if ( x.type == o_phrase )
|
|
{
|
|
ok = do_find_ldr_value(insn, insn.ea, x.secreg, &val2, nest_level+1);
|
|
}
|
|
else if ( x.type == o_displ )
|
|
{
|
|
ok = true;
|
|
val2.value = (int32)x.addr;
|
|
}
|
|
if ( !ok )
|
|
return false;
|
|
}
|
|
int scale1 = 1;
|
|
int scale2 = 1;
|
|
if ( x.membase )
|
|
scale1 = get_scale_factor(insn);
|
|
else
|
|
scale2 = get_scale_factor(insn);
|
|
val_ea = to_ea(insn.cs, scale1 * val1.value + scale2 * val2.value);
|
|
if ( x.dtype == dt_dword && is_const_seg(val_ea) )
|
|
value = get_dword(val_ea);
|
|
}
|
|
if ( ok && lvi != NULL )
|
|
{
|
|
lvi->value = uint32(value);
|
|
if ( value != BADADDR )
|
|
lvi->set_valid(true);
|
|
lvi->val_ea = uint32(val_ea);
|
|
lvi->n = x.n;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
bool reg_tracker_t::do_find_ldr_value(
|
|
const insn_t &insn,
|
|
ea_t ea,
|
|
int reg,
|
|
ldr_value_info_t *p_lvi,
|
|
int nest_level)
|
|
{
|
|
if ( nest_level > 200 )
|
|
return false;
|
|
bool ok = false;
|
|
ldr_value_info_t lvi;
|
|
do
|
|
{
|
|
if ( reg == PCL || reg == NEXT_PC )
|
|
{
|
|
lvi.value = reg == PCL ? insn.ip & ~3ul : insn.ip + insn.size;
|
|
lvi.value &= 0xFFFFFFFF;
|
|
lvi.set_valid(true);
|
|
lvi.set_const();
|
|
lvi.insn_eas.push_back(insn.ea);
|
|
ok = true;
|
|
break;
|
|
}
|
|
|
|
int sreg = get_base_sreg(reg);
|
|
if ( sreg >= 0 )
|
|
{
|
|
lvi.value = get_sreg(ea, sreg);
|
|
if ( lvi.value != BADADDR )
|
|
{
|
|
lvi.set_valid(true);
|
|
lvi.set_const();
|
|
ok = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( reg >= R60 || reg < 0 )
|
|
{
|
|
// not handled
|
|
break;
|
|
}
|
|
|
|
// check if it's in the cache
|
|
reg_values_cache_t::iterator regs_it = regcache.find(ea);
|
|
if ( regs_it != regcache.end() )
|
|
{
|
|
const ldr_value_info_t &cached = regs_it->second.regs[reg];
|
|
if ( cached.is_known() )
|
|
{
|
|
ok = lvi.is_valid();
|
|
if ( ok )
|
|
lvi = cached;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
ushort fbase_reg;
|
|
if ( check_fbase_reg && get_fbase_info(&lvi.value, &fbase_reg) && fbase_reg == reg )
|
|
{
|
|
lvi.value -= to_ea(insn.cs, 0);
|
|
ok = true;
|
|
}
|
|
*/
|
|
|
|
const insn_t *pinsn = &insn;
|
|
insn_t curr_insn;
|
|
while ( !ok )
|
|
{
|
|
flags_t F = get_flags(pinsn->ea);
|
|
if ( has_xref(F) || !is_flow(F) )
|
|
{
|
|
// count xrefs to the current instruction
|
|
xrefblk_t xb;
|
|
int numxrefs = 0;
|
|
ea_t xref_from = BADADDR;
|
|
for ( bool ok2 = xb.first_to(pinsn->ea, XREF_ALL);
|
|
ok2 && numxrefs < 2;
|
|
ok2 = xb.next_to() )
|
|
{
|
|
if ( xb.iscode && xb.from < pinsn->ea ) // count only xrefs from above
|
|
{
|
|
// call xref => bad
|
|
if ( xb.type == fl_CN || xb.type == fl_CF )
|
|
{
|
|
numxrefs = 0;
|
|
break;
|
|
}
|
|
xref_from = xb.from;
|
|
numxrefs++;
|
|
}
|
|
}
|
|
// if we have a single xref, use it
|
|
if ( numxrefs != 1 || xref_from == BADADDR || decode_insn(&curr_insn, xref_from) == 0 )
|
|
break;
|
|
|
|
}
|
|
else
|
|
{
|
|
if ( decode_prev_insn(&curr_insn, pinsn->ea) == BADADDR )
|
|
break;
|
|
}
|
|
pinsn = &curr_insn;
|
|
|
|
// we started with a conditional instruction?
|
|
// (BR.cc does not actually use a condition code)
|
|
if ( has_cond(insn) && insn.itype != ARC_br )
|
|
{
|
|
// ignore instructions which belong to different condition branches
|
|
if ( !has_cond(*pinsn) || pinsn->itype == ARC_br )
|
|
continue;
|
|
if ( get_cond(*pinsn) != get_cond(insn) )
|
|
continue;
|
|
// if current instruction changes flags, stop tracking
|
|
if ( spoils_flags(*pinsn) )
|
|
break;
|
|
}
|
|
|
|
if ( pinsn->Op1.is_reg(reg) )
|
|
{
|
|
switch ( pinsn->itype )
|
|
{
|
|
case ARC_ld:
|
|
if ( pinsn->Op2.type == o_mem && pinsn->Op2.dtype == dt_dword )
|
|
{
|
|
lvi.val_ea = to_ea(pinsn->cs, pinsn->Op2.addr);
|
|
if ( is_loaded(lvi.val_ea) && is_const_seg(lvi.val_ea) )
|
|
{
|
|
lvi.value = get_dword(lvi.val_ea);
|
|
lvi.set_const();
|
|
ok = true;
|
|
}
|
|
}
|
|
else if ( pinsn->Op2.type == o_displ || pinsn->Op2.type == o_phrase )
|
|
{
|
|
ok = do_calc_complex_value(*pinsn, pinsn->Op2, &lvi, nest_level+1) && lvi.is_valid();
|
|
}
|
|
if ( ok )
|
|
lvi.insn_eas.push_back(pinsn->ea);
|
|
break;
|
|
case ARC_mov:
|
|
ok = do_find_op_value(*pinsn, pinsn->Op2, &lvi, nest_level+1);
|
|
if ( ok )
|
|
{
|
|
if ( pinsn->itype == ARC_mov && pinsn->Op2.type == o_imm )
|
|
{
|
|
// MOV Rx, #ABCD
|
|
lvi.val_ea = pinsn->ea;
|
|
lvi.n = 1;
|
|
}
|
|
}
|
|
break;
|
|
case ARC_asr:
|
|
case ARC_lsl:
|
|
case ARC_lsr:
|
|
case ARC_ror:
|
|
case ARC_and:
|
|
case ARC_xor:
|
|
case ARC_add:
|
|
case ARC_sub:
|
|
case ARC_rsub:
|
|
case ARC_or:
|
|
case ARC_bic:
|
|
{
|
|
ldr_value_info_t v1;
|
|
ldr_value_info_t v2;
|
|
const op_t *op1 = &pinsn->Op1;
|
|
const op_t *op2 = &pinsn->Op2;
|
|
if ( pinsn->Op3.type != o_void )
|
|
{ // arm mode
|
|
op1++; // points to pinsn->Op2
|
|
op2++; // points to pinsn->Op3
|
|
}
|
|
if ( !do_find_op_value(*pinsn, *op1, &v1, nest_level+1) )
|
|
break;
|
|
if ( !do_find_op_value(*pinsn, *op2, &v2, nest_level+1) )
|
|
break;
|
|
switch ( pinsn->itype )
|
|
{
|
|
case ARC_add:
|
|
lvi.value = v1.value + v2.value;
|
|
break;
|
|
case ARC_sub:
|
|
lvi.value = v1.value - v2.value;
|
|
break;
|
|
case ARC_rsub:
|
|
lvi.value = v2.value - v1.value;
|
|
break;
|
|
case ARC_or:
|
|
lvi.value = v1.value | v2.value;
|
|
break;
|
|
case ARC_asr:
|
|
lvi.value = ((int32)v1.value) >> v2.value;
|
|
break;
|
|
case ARC_lsl:
|
|
lvi.value = v1.value << v2.value;
|
|
break;
|
|
case ARC_lsr:
|
|
lvi.value = ((uint32)v1.value) >> v2.value;
|
|
break;
|
|
case ARC_ror:
|
|
v2.value %= 32;
|
|
lvi.value = (v1.value >> v2.value) | left_shift(v1.value, 32-v2.value);
|
|
break;
|
|
case ARC_and:
|
|
lvi.value = v1.value & v2.value;
|
|
break;
|
|
case ARC_xor:
|
|
lvi.value = v1.value ^ v2.value;
|
|
break;
|
|
case ARC_bic:
|
|
lvi.value = v1.value & ~v2.value;
|
|
break;
|
|
}
|
|
ok = true;
|
|
if ( v1.is_const() && v2.is_const() )
|
|
lvi.set_const();
|
|
// we do not take into account the insns that calculate .got
|
|
/*
|
|
if ( got_ea == BADADDR || v1.value != got_ea )
|
|
add_eavec(&lvi.insn_eas, v1.insn_eas);
|
|
if ( got_ea == BADADDR || v2.value != got_ea )
|
|
add_eavec(&lvi.insn_eas, v2.insn_eas);*/
|
|
lvi.insn_eas.push_back(pinsn->ea);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else if ( (pinsn->itype == ARC_ld || pinsn->itype == ARC_st)
|
|
&& pinsn->Op2.type == o_displ && pinsn->Op2.reg == reg
|
|
&& ((pinsn->auxpref & aux_amask) == aux_a || (pinsn->auxpref & aux_amask) == aux_ab) )
|
|
{
|
|
// writeback of the base reg
|
|
// find the previous value
|
|
op_t x = pinsn->Op2;
|
|
x.type = o_reg;
|
|
ok = do_find_op_value(*pinsn, x, &lvi, nest_level+1);
|
|
if ( ok )
|
|
{
|
|
// add the immediate
|
|
lvi.value += pinsn->Op2.addr;
|
|
lvi.insn_eas.push_back(pinsn->ea);
|
|
}
|
|
}
|
|
if ( pm.spoils(*pinsn, reg) )
|
|
break;
|
|
}
|
|
#ifdef __EA64__
|
|
lvi.value &= 0xFFFFFFFF;
|
|
#endif
|
|
lvi.set_valid(ok);
|
|
regcache[ea].regs[reg] = lvi;
|
|
}
|
|
while ( false );
|
|
|
|
if ( ok && p_lvi != NULL )
|
|
*p_lvi = lvi;
|
|
return ok;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
bool arc_t::find_op_value_ex(
|
|
const insn_t &insn,
|
|
const op_t &x,
|
|
struct ldr_value_info_t *lvi,
|
|
bool /*check_fbase_reg*/)
|
|
{
|
|
reg_tracker_t tr(this);
|
|
return tr.do_find_op_value(insn, x, lvi, 0);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// find the value loaded into reg
|
|
bool arc_t::find_ldr_value_ex(
|
|
const insn_t &insn,
|
|
ea_t ea,
|
|
int reg,
|
|
struct ldr_value_info_t *lvi,
|
|
bool /*check_fbase_reg*/)
|
|
{
|
|
reg_tracker_t tr(this);
|
|
return tr.do_find_ldr_value(insn, ea, reg, lvi, 0);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
bool arc_t::find_op_value(
|
|
const insn_t &insn,
|
|
const op_t &x,
|
|
uval_t *p_val,
|
|
ea_t *p_val_ea,
|
|
bool check_fbase_reg,
|
|
bool *was_const_load)
|
|
{
|
|
ldr_value_info_t tmp;
|
|
if ( find_op_value_ex(insn, x, &tmp, check_fbase_reg) )
|
|
{
|
|
if ( p_val != NULL )
|
|
*p_val = tmp.value;
|
|
if ( p_val_ea != NULL )
|
|
*p_val_ea = tmp.val_ea;
|
|
if ( was_const_load != NULL )
|
|
*was_const_load = tmp.is_const();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
bool arc_t::find_ldr_value(
|
|
const insn_t &insn,
|
|
ea_t ea,
|
|
int reg,
|
|
uval_t *p_val,
|
|
ea_t *p_val_ea,
|
|
bool check_fbase_reg,
|
|
bool *was_const_load)
|
|
{
|
|
ldr_value_info_t tmp;
|
|
if ( find_ldr_value_ex(insn, ea, reg, &tmp, check_fbase_reg) )
|
|
{
|
|
if ( p_val != NULL )
|
|
*p_val = tmp.value;
|
|
if ( p_val_ea != NULL )
|
|
*p_val_ea = tmp.val_ea;
|
|
if ( was_const_load != NULL )
|
|
*was_const_load = tmp.is_const();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// 4 sub rA, rA', #minv (optional)
|
|
// 3 cmp rA, #size | brhs rA, #size, default
|
|
// bhi default or |
|
|
// bls body with optional 'b default'
|
|
// body:
|
|
// 2 ldb.x rA, [rJumps,rA'] | ld.as rA, [#jumps,rA'] (if not using bi/bih)
|
|
// 1 add1 rA, rElbase, rA' (optional)
|
|
// 0 j [rA] | bi [rA] | bih [rA]
|
|
|
|
static const char arc_depends[][4] =
|
|
{
|
|
{ 1 | JPT_OPT }, // 0
|
|
{ 2 }, // 1 optional
|
|
{ 3 }, // 2 if and only if not using bi/bih
|
|
{ 4 | JPT_OPT | JPT_NEAR }, // 3
|
|
{ 0 }, // 4 optional
|
|
};
|
|
|
|
struct arc_jump_pattern_t : public jump_pattern_t
|
|
{
|
|
protected:
|
|
enum { rA, rC };
|
|
enum
|
|
{
|
|
BODY_NJPI = 2, // ldb.x rA, [rJumps,rA']
|
|
ELBASE_NJPI = 1, // add1 rA, rElbase, rA'
|
|
};
|
|
arc_t ±
|
|
ea_t jumps_offset_ea;
|
|
int jumps_offset_n;
|
|
ea_t elbase_offset_ea; //lint !e958 padding is required
|
|
int elbase_offset_n;
|
|
|
|
public:
|
|
arc_jump_pattern_t(procmod_t *_pm, switch_info_t *_si)
|
|
: jump_pattern_t(_si, arc_depends, rC),
|
|
pm(*(arc_t *)_pm),
|
|
jumps_offset_ea(BADADDR),
|
|
jumps_offset_n(-1),
|
|
elbase_offset_ea(BADADDR),
|
|
elbase_offset_n(-1)
|
|
{
|
|
modifying_r32_spoils_r64 = false;
|
|
si->flags |= SWI_HXNOLOWCASE;
|
|
non_spoiled_reg = rA;
|
|
}
|
|
|
|
virtual void process_delay_slot(ea_t &ea, bool branch) const override;
|
|
virtual bool equal_ops(const op_t &x, const op_t &y) const override;
|
|
virtual bool handle_mov(tracked_regs_t &_regs) override;
|
|
virtual void check_spoiled(tracked_regs_t *_regs) const override;
|
|
|
|
bool jpi4() override; // sub rA, rA', #minv
|
|
bool jpi3() override; // cmp followed by the conditional jump or 'brhi/lo'
|
|
bool jpi2() override; // ldb.x rA, [rJumps,rA']
|
|
bool jpi1() override; // add1 rA, rElbase, rA'
|
|
bool jpi0() override; // j [rA] | bi [rA] | bih [rA]
|
|
|
|
//lint -esym(1762, arc_jump_pattern_t::finish) member function could be made const
|
|
bool finish();
|
|
|
|
protected:
|
|
static inline bool optype_supported(const op_t &x);
|
|
|
|
// helpers
|
|
// brhs rA, #size, default | brlo rA, #size, body
|
|
bool jpi_cmp_jump(const op_t **op_var);
|
|
// bhi default | bls body with optional 'b default'
|
|
bool jpi_condjump();
|
|
// cmp rA, #size
|
|
bool jpi_cmp_ncases(const op_t **op_var);
|
|
|
|
// prepare and track rC
|
|
bool analyze_cond(cond_t cond, ea_t jump);
|
|
};
|
|
|
|
//-------------------------------------------------------------------------
|
|
void arc_jump_pattern_t::process_delay_slot(ea_t &ea, bool branch) const
|
|
{
|
|
flags_t F = get_flags(ea);
|
|
if ( !is_code(F) )
|
|
return;
|
|
insn_t insn2;
|
|
if ( branch )
|
|
{
|
|
if ( decode_insn(&insn2, ea) != 0 && has_dslot(insn2) )
|
|
ea += insn2.size;
|
|
}
|
|
/*else
|
|
{
|
|
return; // no 'likely' insn in ARC
|
|
}*/
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
bool arc_jump_pattern_t::equal_ops(const op_t &x, const op_t &y) const
|
|
{
|
|
if ( x.type != y.type )
|
|
return false;
|
|
// ignore difference in the data size of registers
|
|
switch ( x.type )
|
|
{
|
|
case o_void:
|
|
// consider spoiled values as not equal
|
|
return false;
|
|
case o_reg:
|
|
return x.reg == y.reg;
|
|
case o_displ:
|
|
return x.phrase == y.phrase && x.addr == y.addr;
|
|
case o_condjump:
|
|
// we do not track the condition flags
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
inline bool arc_jump_pattern_t::optype_supported(const op_t &x)
|
|
{
|
|
// we can work with the following types only
|
|
return x.type == o_reg && x.reg <= R63
|
|
|| x.type == o_displ;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
bool arc_jump_pattern_t::handle_mov(tracked_regs_t &_regs)
|
|
{
|
|
const op_t *src = &insn.Op2;
|
|
const op_t *dst = &insn.Op1;
|
|
switch ( insn.itype )
|
|
{
|
|
case ARC_add:
|
|
case ARC_lsl:
|
|
case ARC_lsr:
|
|
case ARC_sub:
|
|
case ARC_xor:
|
|
case ARC_or:
|
|
if ( insn.Op3.type != o_imm || insn.Op3.value != 0 )
|
|
return false;
|
|
// no break
|
|
case ARC_ld:
|
|
case ARC_mov:
|
|
break;
|
|
case ARC_st:
|
|
std::swap(src, dst);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
if ( !optype_supported(*src) || optype_supported(*dst) )
|
|
return false;
|
|
return set_moved(*dst, *src, _regs);
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
void arc_jump_pattern_t::check_spoiled(tracked_regs_t *__regs) const
|
|
{
|
|
tracked_regs_t &_regs = *__regs;
|
|
for ( uint i = 0; i < _regs.size(); ++i )
|
|
{
|
|
const op_t &x = _regs[i];
|
|
if ( x.type == o_reg && pm.spoils(insn, x.reg)
|
|
|| x.type == o_condjump && spoils_flags(insn) )
|
|
{
|
|
set_spoiled(&_regs, x);
|
|
}
|
|
}
|
|
check_spoiled_not_reg(&_regs, PROC_MAXCHGOP);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// j [rA]
|
|
bool arc_jump_pattern_t::jpi0()
|
|
{
|
|
if ( insn.itype == ARC_bi || insn.itype == ARC_bih )
|
|
{
|
|
si->jumps = insn.ea + insn.size;
|
|
si->flags |= SWI_JMPINSN;
|
|
si->set_jtable_element_size(insn.itype == ARC_bi ? 4 : 2);
|
|
|
|
skip[1] = true; // no jpi2
|
|
skip[2] = true; // no jpi2
|
|
track(insn.Op1.secreg, rA, dt_dword);
|
|
return true;
|
|
}
|
|
|
|
if ( insn.itype != ARC_j
|
|
|| insn.Op1.type != o_displ
|
|
|| insn.Op1.addr != 0
|
|
|| has_cond(insn) )
|
|
{
|
|
return false;
|
|
}
|
|
track(insn.Op1.phrase, rA, dt_dword);
|
|
return true;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// add1 rA, rElbase, rA'
|
|
bool arc_jump_pattern_t::jpi1()
|
|
{
|
|
if ( insn.itype != ARC_add1
|
|
&& insn.itype != ARC_add2
|
|
&& insn.itype != ARC_add
|
|
|| has_cond(insn)
|
|
|| !is_equal(insn.Op1, rA) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ea_t elbase;
|
|
const op_t *op_var;
|
|
if ( insn.Op2.type == o_imm && optype_supported(insn.Op3) )
|
|
{
|
|
elbase = insn.Op2.value;
|
|
elbase_offset_ea = insn.ea;
|
|
elbase_offset_n = 1;
|
|
op_var = &insn.Op3;
|
|
}
|
|
else if ( insn.itype == ARC_add
|
|
&& insn.Op3.type == o_imm
|
|
&& optype_supported(insn.Op2) )
|
|
{
|
|
elbase = insn.Op3.value;
|
|
elbase_offset_ea = insn.ea;
|
|
elbase_offset_n = 2;
|
|
op_var = &insn.Op2;
|
|
}
|
|
else
|
|
{
|
|
ldr_value_info_t lvi;
|
|
if ( insn.Op2.type == o_reg
|
|
&& optype_supported(insn.Op3)
|
|
&& pm.find_ldr_value_ex(insn, insn.ea, insn.Op2.reg, &lvi, true) )
|
|
{
|
|
op_var = &insn.Op3;
|
|
}
|
|
else if ( insn.itype == ARC_add
|
|
&& insn.Op3.type == o_reg
|
|
&& optype_supported(insn.Op2)
|
|
&& pm.find_ldr_value_ex(insn, insn.ea, insn.Op3.reg, &lvi, true) )
|
|
{
|
|
op_var = &insn.Op2;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
elbase = lvi.value;
|
|
elbase_offset_ea = lvi.val_ea;
|
|
elbase_offset_n = lvi.n;
|
|
}
|
|
|
|
si->set_elbase(elbase);
|
|
if ( insn.itype == ARC_add1 )
|
|
si->set_shift(1);
|
|
else if ( insn.itype == ARC_add2 )
|
|
si->set_shift(2);
|
|
trackop(*op_var, rA);
|
|
return true;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// ldb.x rA, [rJumps,rA']
|
|
// ldb.x rA, [rA',rJumps]
|
|
// ldw.as rA, [rJumps,rA']
|
|
// ldw.x rA, [rA',rJumps]
|
|
// ld.x.as rA, [#jumps,rA']
|
|
// ldb rA, [rA',#jumps]
|
|
bool arc_jump_pattern_t::jpi2()
|
|
{
|
|
if ( insn.itype != ARC_ld
|
|
|| insn.Op2.type != o_displ && insn.Op2.type != o_phrase
|
|
|| !is_equal(insn.Op1, rA) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int elsize;
|
|
switch ( insn.auxpref & aux_zmask )
|
|
{
|
|
case aux_b:
|
|
elsize = 1;
|
|
break;
|
|
case aux_w:
|
|
elsize = 2;
|
|
break;
|
|
case aux_l:
|
|
elsize = 4;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
int reg_var = -1;
|
|
|
|
// do we have scaled load?
|
|
switch ( insn.auxpref & aux_amask )
|
|
{
|
|
case aux_anone:
|
|
if ( elsize != 1 )
|
|
{
|
|
// check for preceding scale instruction
|
|
// 2: asl r12, r1 (shift by one)
|
|
// 4: asl r12, r1, 2
|
|
insn_t prev;
|
|
if ( decode_prev_insn(&prev, insn.ea) != BADADDR
|
|
&& prev.itype == ARC_asl
|
|
&& is_equal(prev.Op1, rA) )
|
|
{
|
|
if ( elsize == 2 && prev.Op3.type == o_void
|
|
|| elsize == 2 && prev.Op3.type == o_imm && prev.Op3.value == 2 )
|
|
{
|
|
reg_var = prev.Op2.reg;
|
|
break;
|
|
}
|
|
}
|
|
return false;
|
|
|
|
}
|
|
break;
|
|
case aux_as:
|
|
// nothing to do, index is scaled during load
|
|
break;
|
|
default:
|
|
// writeback or pre-increment: not valid here
|
|
return false;
|
|
}
|
|
|
|
const op_t &x = insn.Op2;
|
|
ea_t jumps;
|
|
if ( x.type == o_phrase )
|
|
{
|
|
ldr_value_info_t lvi;
|
|
if ( reg_var == -1 )
|
|
{
|
|
if ( pm.find_ldr_value_ex(insn, insn.ea, x.phrase, &lvi, true) )
|
|
{
|
|
reg_var = x.secreg;
|
|
}
|
|
else if ( elsize == 1
|
|
&& pm.find_ldr_value_ex(insn, insn.ea, x.secreg, &lvi, true) )
|
|
{
|
|
reg_var = x.phrase;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
jumps = lvi.value;
|
|
jumps_offset_ea = lvi.val_ea;
|
|
jumps_offset_n = lvi.n;
|
|
}
|
|
// x.type == o_displ
|
|
else if ( x.type == o_displ )
|
|
{
|
|
if ( reg_var == -1 )
|
|
{
|
|
if ( x.membase != 1 && elsize != 1 )
|
|
return false;
|
|
reg_var = x.phrase;
|
|
}
|
|
jumps = x.addr;
|
|
jumps_offset_ea = insn.ea;
|
|
jumps_offset_n = 1;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
si->jumps = jumps;
|
|
si->set_jtable_element_size(elsize);
|
|
if ( (insn.auxpref & aux_x) != 0 )
|
|
si->flags |= SWI_SIGNED;
|
|
track(reg_var, rA, dt_dword);
|
|
return true;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// cmp followed by the conditional jump or 'brhi/lo'
|
|
bool arc_jump_pattern_t::jpi3()
|
|
{
|
|
// var should not be spoiled
|
|
QASSERT(10312, !is_spoiled(rA));
|
|
|
|
const op_t *op_var;
|
|
if ( !jpi_cmp_jump(&op_var)
|
|
&& (jpi_condjump() // continue matching if found
|
|
|| is_spoiled(rC)
|
|
|| !jpi_cmp_ncases(&op_var)) )
|
|
{
|
|
return false;
|
|
}
|
|
op_t &op = regs[rC];
|
|
// assert: op.type == o_condjump
|
|
if ( (op.value & cc_inc_ncases) != 0 )
|
|
++si->ncases;
|
|
si->defjump = op.specval;
|
|
si->set_expr(op_var->reg, op_var->dtype);
|
|
return true;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// sub rA, rA', #minv
|
|
bool arc_jump_pattern_t::jpi4()
|
|
{
|
|
if ( insn.itype != ARC_sub
|
|
|| has_cond(insn)
|
|
|| insn.Op3.type != o_imm
|
|
|| !is_equal(insn.Op1, rA) )
|
|
{
|
|
return false;
|
|
}
|
|
si->lowcase = insn.Op3.value;
|
|
return true;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
bool arc_jump_pattern_t::finish()
|
|
{
|
|
if ( !skip[2] )
|
|
{
|
|
if ( eas[ELBASE_NJPI] != BADADDR && elbase_offset_ea != BADADDR )
|
|
op_offset(elbase_offset_ea, elbase_offset_n, REF_OFF32);
|
|
if ( jumps_offset_ea != BADADDR )
|
|
op_offset(jumps_offset_ea, jumps_offset_n, REF_OFF32);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// brhs rA, #size, default
|
|
// brlo #size, rA, default
|
|
// brlo rA, #size, body
|
|
// brhs #size, rA, body
|
|
bool arc_jump_pattern_t::jpi_cmp_jump(const op_t **op_var)
|
|
{
|
|
if ( insn.itype != ARC_br
|
|
|| insn.Op3.type != o_near
|
|
|| !has_core_cond(insn) )
|
|
{
|
|
return false;
|
|
}
|
|
cond_t cond = get_core_cond(insn);
|
|
if ( cond != cLO && cond != cHS )
|
|
return false;
|
|
uval_t size;
|
|
if ( insn.Op1.type == o_reg && insn.Op2.type == o_imm )
|
|
{
|
|
*op_var = &insn.Op1;
|
|
size = insn.Op2.value;
|
|
}
|
|
else if ( insn.Op1.type == o_imm && insn.Op2.type == o_reg )
|
|
{
|
|
cond = invert_cond(cond);
|
|
*op_var = &insn.Op2;
|
|
size = insn.Op1.value;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
if ( !analyze_cond(cond, to_ea(insn.cs, insn.Op3.addr)) )
|
|
return false;
|
|
si->ncases = ushort(size);
|
|
trackop(**op_var, rA);
|
|
return true;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// bhi default | bls body with optional 'b default'
|
|
bool arc_jump_pattern_t::jpi_condjump()
|
|
{
|
|
if ( insn.itype != ARC_b
|
|
|| insn.Op1.type != o_near
|
|
|| !has_core_cond(insn) )
|
|
{
|
|
return false;
|
|
}
|
|
return analyze_cond(get_core_cond(insn), to_ea(insn.cs, insn.Op1.addr));
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// cmp rA, #size
|
|
bool arc_jump_pattern_t::jpi_cmp_ncases(const op_t **op_var)
|
|
{
|
|
// assert: !is_spoiled(rA) because rA is non spoiled register
|
|
if ( insn.itype != ARC_cmp
|
|
|| has_cond(insn)
|
|
|| insn.Op2.type != o_imm
|
|
|| !same_value(insn.Op1, rA) )
|
|
{
|
|
return false;
|
|
}
|
|
si->ncases = ushort(insn.Op2.value);
|
|
// continue to track rA
|
|
*op_var = &insn.Op1;
|
|
return true;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// prepare and track rC
|
|
bool arc_jump_pattern_t::analyze_cond(cond_t cond, ea_t jump)
|
|
{
|
|
op_t op;
|
|
op.type = o_condjump;
|
|
op.value = 0;
|
|
switch ( cond )
|
|
{
|
|
case cHI: // higher
|
|
case cLS: // lower or same
|
|
case cGT:
|
|
case cLE:
|
|
op.value |= cc_inc_ncases;
|
|
break;
|
|
case cLO: // lower
|
|
case cHS: // higher or same
|
|
case cLT:
|
|
case cGE:
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
switch ( cond )
|
|
{
|
|
case cHI: // higher
|
|
case cHS: // higher or same
|
|
case cGT:
|
|
case cGE:
|
|
op.specval = jump;
|
|
break;
|
|
case cLO: // lower
|
|
case cLS: // lower or same
|
|
case cLT:
|
|
case cLE:
|
|
// we have conditional jump to the switch body
|
|
{
|
|
ea_t body = eas[BODY_NJPI];
|
|
// assert: body != BADADDR
|
|
if ( jump > body )
|
|
return false;
|
|
op.specval = insn.ea + insn.size;
|
|
|
|
// possibly followed by 'b default'
|
|
insn_t dflt;
|
|
if ( decode_insn(&dflt, op.specval) > 0
|
|
&& dflt.itype == ARC_b
|
|
&& !has_cond(insn)
|
|
&& !has_dslot(insn)
|
|
&& dflt.Op1.type == o_near )
|
|
{
|
|
op.specval = to_ea(dflt.cs, dflt.Op1.addr);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
op.addr = insn.ea;
|
|
trackop(op, rC);
|
|
return true;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
static int is_jump_pattern(switch_info_t *si, const insn_t &insn, procmod_t *pm)
|
|
{
|
|
arc_jump_pattern_t jp(pm, si);
|
|
if ( !jp.match(insn) || !jp.finish() )
|
|
return JT_NONE;
|
|
return JT_SWITCH;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
bool arc_is_switch(switch_info_t *si, const insn_t &insn)
|
|
{
|
|
if ( insn.itype != ARC_j
|
|
&& insn.itype != ARC_bi
|
|
&& insn.itype != ARC_bih )
|
|
return false;
|
|
|
|
static is_pattern_t *const patterns[] =
|
|
{
|
|
is_jump_pattern,
|
|
};
|
|
return check_for_table_jump(si, insn, patterns, qnumber(patterns));
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Trace the value of the SP and create an SP change point if the current
|
|
// instruction modifies the SP.
|
|
sval_t arc_t::calc_sp_delta(const insn_t &insn)
|
|
{
|
|
if ( has_cond(insn) ) // trace only unconditional instructions
|
|
return 0; // conditional instructions may be
|
|
// corrected manually
|
|
switch ( insn.itype )
|
|
{
|
|
case ARC_add:
|
|
case ARC_sub:
|
|
if ( insn.Op1.is_reg(SP) && insn.Op2.is_reg(SP) )
|
|
{
|
|
// add sp, sp, #imm
|
|
// add sp, sp, r1
|
|
uval_t spofs;
|
|
if ( find_op_value(insn, insn.Op3, &spofs, NULL, false) && (spofs & 3) == 0 )
|
|
return insn.itype == ARC_sub ? -spofs : spofs;
|
|
}
|
|
break;
|
|
case ARC_push: // push [reg]
|
|
return -4;
|
|
case ARC_pop: // pop [reg]
|
|
return +4;
|
|
case ARC_ld: // ld.ab fp, [sp,4]
|
|
case ARC_st: // st.a fp, [sp,-4]
|
|
if ( insn.Op2.type == o_displ
|
|
&& insn.Op2.reg == SP
|
|
&& ((insn.auxpref & aux_amask) == aux_a || (insn.auxpref & aux_amask) == aux_ab) )
|
|
{
|
|
if ( (insn.Op2.addr & 3) == 0 )
|
|
return insn.Op2.addr;
|
|
}
|
|
break;
|
|
case ARC_bl: // bl __ac_push_13_to_NN: push 13..NN
|
|
case ARC_b: // b __ac_pop_13_to_NN: pop 13..NN,blink
|
|
{
|
|
ea_t call_ea = to_ea(insn.cs, insn.Op1.addr);
|
|
sval_t delta;
|
|
if ( is_millicode(call_ea, &delta) )
|
|
{
|
|
if ( delta == BADADDR )
|
|
break;
|
|
return delta;
|
|
}
|
|
}
|
|
break;
|
|
case ARC_enter:
|
|
case ARC_leave:
|
|
{
|
|
sval_t nregs = insn.Op1.reglist & REGLIST_REGS;
|
|
nregs += (insn.Op1.reglist & REGLIST_FP) != 0;
|
|
nregs += (insn.Op1.reglist & REGLIST_BLINK) != 0;
|
|
|
|
return 4 * (insn.itype == ARC_enter ? -nregs : nregs);
|
|
}
|
|
default:
|
|
if ( insn.Op1.is_reg(SP) && insn.itype != ARC_mov )
|
|
{
|
|
// msg("??? illegal access mode sp @ %a\n", insn.ea);
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Add a SP change point. We assume that SP is always divisible by 4
|
|
inline void add_stkpnt(const insn_t &insn, func_t *pfn, sval_t v)
|
|
{
|
|
add_auto_stkpnt(pfn, insn.ea+insn.size, v);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Trace the value of the SP and create an SP change point if the current
|
|
// instruction modifies the SP.
|
|
void arc_t::trace_sp(const insn_t &insn)
|
|
{
|
|
func_t *pfn = get_func(insn.ea);
|
|
if ( pfn == NULL )
|
|
return; // no function -> we don't care about SP
|
|
|
|
sval_t delta = calc_sp_delta(insn);
|
|
if ( delta != 0 )
|
|
add_stkpnt(insn, pfn, delta);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
bool arc_t::arc_calc_spdelta(sval_t *spdelta, const insn_t &insn)
|
|
{
|
|
*spdelta = calc_sp_delta(insn);
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// is the input file object file?
|
|
// in such files, the references will be fixed up by the linker
|
|
static bool is_object_file(void)
|
|
{
|
|
// Currently we know only about ELF relocatable files
|
|
if ( inf_get_filetype() == f_ELF )
|
|
{
|
|
char buf[MAXSTR];
|
|
if ( get_file_type_name(buf, sizeof(buf)) > 0
|
|
&& stristr(buf, "reloc") != NULL ) // ELF (Relocatable)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// force the offset by the calculated base
|
|
void arc_t::force_offset(
|
|
ea_t ea,
|
|
int n,
|
|
ea_t base,
|
|
bool issub,
|
|
int scale)
|
|
{
|
|
if ( !is_off(get_flags(ea), n)
|
|
|| !is_object_file() && get_offbase(ea, n) != base )
|
|
{
|
|
refinfo_t ri;
|
|
|
|
reftype_t reftype = REF_OFF32;
|
|
if ( scale == 2 )
|
|
reftype = ref_arcsoh_id | REFINFO_CUSTOM;
|
|
else if ( scale == 4 )
|
|
reftype = ref_arcsol_id | REFINFO_CUSTOM;
|
|
|
|
ri.init(reftype|REFINFO_NOBASE|(issub ? REFINFO_SUBTRACT : 0), base);
|
|
op_offset_ex(ea, n, &ri);
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// add resolved target address, to be displayed as a comment
|
|
inline void arc_t::add_dxref(const insn_t &insn, ea_t target)
|
|
{
|
|
// only add it if the comment would not be displayed otherwise
|
|
// ASCII xrefs show up as comments
|
|
if ( (inf_get_strlit_flags() & STRF_COMMENT) && is_strlit(get_flags(target)) )
|
|
return;
|
|
|
|
// repeatable comments follow xrefs
|
|
if ( get_cmt(NULL, target, true) > 0 )
|
|
return;
|
|
|
|
// demangled names show as comments
|
|
// FIXME: get rid of GN_INSNLOC
|
|
#define MY_GN_INSNLOC 0x0080
|
|
if ( get_demangled_name(NULL, target, inf_get_short_demnames(),
|
|
DEMNAM_CMNT, GN_STRICT|MY_GN_INSNLOC) > 0 )
|
|
return;
|
|
|
|
set_dxref(insn.ea, target);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
static bool is_good_target(ea_t ea)
|
|
{
|
|
// address must exist
|
|
if ( !is_mapped(ea) )
|
|
return false;
|
|
|
|
flags_t F = get_flags(ea);
|
|
if ( !is_code(F) )
|
|
return true;
|
|
|
|
// don't point into middle of instructions
|
|
return !is_tail(F);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Emulate an instruction
|
|
int arc_t::emu(const insn_t &insn)
|
|
{
|
|
uint32 Feature = insn.get_canon_feature(ph);
|
|
|
|
islast = Feature & CF_STOP;
|
|
|
|
ea_t cmdend = insn.ea + insn.size;
|
|
|
|
if ( helper.altval_ea(insn.ea, DSLOT_TAG) == 1 )
|
|
islast = 1; // previous instruction was an unconditional jump/branch
|
|
|
|
// you may emulate selected instructions with a greater care:
|
|
switch ( insn.itype )
|
|
{
|
|
case ARC_j:
|
|
case ARC_b:
|
|
if ( !has_cond(insn) ) // branch always
|
|
islast = 1;
|
|
break;
|
|
case ARC_bi:
|
|
case ARC_bih:
|
|
islast = 1;
|
|
break;
|
|
case ARC_leave:
|
|
if ( (insn.Op1.reglist & REGLIST_PCL) != 0 ) // branch to blink
|
|
islast = 1;
|
|
break;
|
|
case ARC_add: // add r1, r2, #imm
|
|
case ARC_sub: // sub r1, r2, #imm
|
|
if ( (idpflags & ARC_TRACKREGS) != 0
|
|
&& insn.Op1.type == o_reg
|
|
&& !is_stkptr(insn, insn.Op2.reg)
|
|
&& !is_defarg(get_flags(insn.ea), 2) )
|
|
{
|
|
bool issub = insn.itype == ARC_sub;
|
|
ea_t val1 = BADADDR;
|
|
if ( find_op_value(insn, insn.Op2, &val1) && val1 != 0 )
|
|
{
|
|
if ( insn.Op3.type == o_imm && insn.Op3.value > 3 && is_good_target(val1 + insn.Op3.value) )
|
|
{
|
|
force_offset(insn.ea, 2, val1, issub);
|
|
}
|
|
else if ( insn.Op2.reg != insn.Op3.reg )
|
|
{
|
|
// mov r12, #imm
|
|
// sub r3, r15, r12
|
|
ldr_value_info_t lvi;
|
|
if ( find_op_value_ex(insn, insn.Op3, &lvi, false) && lvi.value > 3 )
|
|
{
|
|
ea_t target = issub ? (val1 - lvi.value) : (val1 + lvi.value);
|
|
if ( is_good_target(target) )
|
|
{
|
|
force_offset(lvi.val_ea, lvi.n, val1, issub);
|
|
add_dxref(insn, target & 0xFFFFFFFF);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case ARC_ld: // ld r1, [r2, #imm]
|
|
case ARC_st: // st r1, [r2, #imm]
|
|
if ( (idpflags & ARC_TRACKREGS) != 0
|
|
&& insn.Op2.type == o_displ
|
|
&& !is_stkptr(insn, insn.Op2.reg)
|
|
&& !is_defarg(get_flags(insn.ea), 1) )
|
|
{
|
|
ea_t val1 = BADADDR;
|
|
if ( insn.Op2.addr > 3 && find_ldr_value(insn, insn.ea, insn.Op2.reg, &val1) && val1 != 0 )
|
|
{
|
|
if ( (insn.auxpref & aux_amask) == aux_ab ) // post-increment
|
|
val1 -= insn.Op2.addr;
|
|
if ( is_good_target(val1 + insn.Op2.addr) )
|
|
force_offset(insn.ea, 1, val1, false, get_scale_factor(insn));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
// trace the stack pointer if:
|
|
// - it is the second analysis pass
|
|
// - the stack pointer tracing is allowed
|
|
if ( may_trace_sp() )
|
|
{
|
|
if ( !islast )
|
|
trace_sp(insn); // trace modification of SP register
|
|
else
|
|
recalc_spd(insn.ea); // recalculate SP register for the next insn
|
|
}
|
|
|
|
for ( int i = 0; i < PROC_MAXOP; ++i )
|
|
{
|
|
if ( has_cf_use(Feature, i) )
|
|
handle_operand(insn, insn.ops[i], true);
|
|
}
|
|
|
|
for ( int i = 0; i < PROC_MAXOP; ++i )
|
|
{
|
|
if ( has_cf_chg(Feature, i) )
|
|
handle_operand(insn, insn.ops[i], false);
|
|
}
|
|
|
|
// if the execution flow is not stopped here, then create
|
|
// a xref to the next instruction.
|
|
// Thus we plan to analyze the next instruction.
|
|
|
|
if ( !islast || has_dslot(insn) )
|
|
add_cref(insn.ea, cmdend, fl_F);
|
|
else if ( get_auto_state() == AU_USED )
|
|
recalc_spd(insn.ea);
|
|
|
|
if ( has_dslot(insn) )
|
|
{
|
|
// mark the following address as a delay slot
|
|
int slotkind;
|
|
if ( insn.itype == ARC_bl || insn.itype == ARC_jl )
|
|
slotkind = 3;
|
|
else
|
|
slotkind = islast ? 1 : 2;
|
|
helper.altset_ea(cmdend, slotkind, DSLOT_TAG);
|
|
}
|
|
else
|
|
{
|
|
helper.altdel_ea(cmdend, DSLOT_TAG);
|
|
}
|
|
return 1; // actually the return value is unimportant, but let's it be so
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
bool idaapi create_func_frame(func_t * pfn)
|
|
{
|
|
ea_t ea = pfn->start_ea;
|
|
|
|
insn_t insn;
|
|
for ( int i = 0; i < 10 && ea < pfn->end_ea; i++ )
|
|
{
|
|
if ( !decode_insn(&insn, ea) )
|
|
break;
|
|
// move fp, sp
|
|
// enter_s [...,fp,...]
|
|
if ( insn.itype == ARC_mov
|
|
&& insn.Op1.is_reg(FP)
|
|
&& insn.Op2.is_reg(SP)
|
|
|| insn.itype == ARC_enter
|
|
&& (insn.Op1.reglist & REGLIST_FP) != 0 )
|
|
{
|
|
pfn->flags |= FUNC_FRAME;
|
|
update_func(pfn);
|
|
}
|
|
// sub sp, sp
|
|
if ( insn.itype == ARC_sub
|
|
&& insn.Op1.is_reg(SP)
|
|
&& insn.Op2.is_reg(SP)
|
|
&& insn.Op3.type == o_imm )
|
|
{
|
|
return add_frame(pfn, insn.Op3.value, 0, 0);
|
|
}
|
|
ea += insn.size;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
int idaapi is_sp_based(const insn_t &insn, const op_t & x)
|
|
{
|
|
int flag = OP_FP_BASED;
|
|
if ( x.type == o_displ && x.reg == SP
|
|
|| (x.type == o_imm && x.n == 2 && insn.itype == ARC_add && !insn.Op2.is_reg(FP)) )
|
|
{
|
|
// add rx, sp, #imm
|
|
flag = OP_SP_BASED;
|
|
}
|
|
return OP_SP_ADD | flag;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
int idaapi arc_get_frame_retsize(const func_t * /*pfn */ )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// #processor_t.is_align_insn
|
|
//----------------------------------------------------------------------
|
|
// Is the instruction created only for alignment purposes?
|
|
// returns: number of bytes in the instruction
|
|
int arc_t::is_align_insn(ea_t ea) const
|
|
{
|
|
if ( ptype == prc_arcompact )
|
|
{
|
|
if ( (ea & 3) != 0 )
|
|
return 0;
|
|
if ( get_word(ea) == 0x78E0 ) // nop_s
|
|
return 2;
|
|
if ( get_word(ea) == 0x264A && get_word(ea+2) == 0x7000 ) // mov 0, 0
|
|
return 4;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
static bool can_be_data(ea_t target)
|
|
{
|
|
if ( (target & 3) == 0 )
|
|
{
|
|
segment_t *seg = getseg(target);
|
|
if ( seg == NULL )
|
|
return false;
|
|
if ( seg->start_ea == target )
|
|
return true;
|
|
ea_t prev = prev_head(target, seg->start_ea);
|
|
if ( prev != BADADDR && is_data(get_flags(prev)) )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// we have a possible reference from current instruction to 'target'
|
|
// check if we should make it an offset
|
|
bool arc_t::good_target(const insn_t &insn, ea_t target) const
|
|
{
|
|
if ( target <= ' ' )
|
|
return false;
|
|
|
|
// check if it points to code
|
|
flags_t F = get_flags(target&~1);
|
|
if ( is_code(F) )
|
|
{
|
|
// arcompact code references should have bit 0 set
|
|
if ( ptype == prc_arcompact && ((target & 1) == 0) )
|
|
return false;
|
|
|
|
// arc4 should be word-aligned
|
|
if ( ptype == prc_arc && ((target & 3) != 0) )
|
|
return false;
|
|
|
|
if ( !is_head(F) ) // middle of instruction?
|
|
return false;
|
|
|
|
// if we're referencing middle of a function, it should be the same function
|
|
func_t *pfn = get_func(target);
|
|
if ( pfn == NULL && is_flow(F) )
|
|
return false;
|
|
if ( pfn != NULL && pfn->start_ea != target && !func_contains(pfn, insn.ea) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
else if ( is_data(F) || segtype(target) == SEG_DATA || can_be_data(target) )
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// returns target address
|
|
bool arc_t::copy_insn_optype(const insn_t &insn, const op_t &x, ea_t ea, void *value, bool force) const
|
|
{
|
|
flags_t F = get_flags(ea);
|
|
flags_t iflag = get_flags(insn.ea);
|
|
if ( is_dword(F) && x.dtype == dt_dword
|
|
|| is_word(F) && x.dtype == dt_word
|
|
|| is_byte(F) && x.dtype == dt_byte
|
|
|| is_float(F) && x.dtype == dt_float
|
|
|| is_double(F) && x.dtype == dt_double )
|
|
{
|
|
if ( force || is_defarg(F, 0) && is_defarg(iflag, x.n) )
|
|
{
|
|
// both are defined - check that the data types are the same
|
|
// if not, copy insntype -> dwordtype
|
|
flags_t fd = get_optype_flags0(F);
|
|
flags_t fi = get_optype_flags0(x.n ? (iflag>>4) : iflag);
|
|
if ( fd != fi )
|
|
{
|
|
F = (F ^ fd) | fi;
|
|
opinfo_t ti;
|
|
get_opinfo(&ti, insn.ea, x.n, iflag);
|
|
set_opinfo(ea, 0, F, &ti);
|
|
set_op_type(ea, F, 0);
|
|
plan_ea(insn.ea);
|
|
plan_ea(ea);
|
|
}
|
|
}
|
|
if ( x.dtype == dt_dword )
|
|
{
|
|
if ( !is_defarg(F, 0) || (is_off(F, 0) && get_offbase(ea, 0) == to_ea(insn.cs, 0)) )
|
|
{
|
|
uint32 pcval = get_dword(ea);
|
|
ea_t target = to_ea(insn.cs, pcval);
|
|
// if the data is a 32-bit value which can be interpreted as an address
|
|
// then convert it to an offset expression
|
|
if ( get_auto_state() == AU_USED
|
|
// && (inf.af & AF_DATOFF) != 0
|
|
// && target > ' '
|
|
&& good_target(insn, target) )
|
|
{
|
|
if ( !is_defarg(F, 0) )
|
|
op_plain_offset(ea, 0, to_ea(insn.cs, 0));
|
|
if ( !is_defarg(get_flags(insn.ea), x.n) )
|
|
{
|
|
op_plain_offset(insn.ea, x.n, to_ea(insn.cs, 0));
|
|
}
|
|
}
|
|
// add xref from "LDR Rx,=addr" to addr.
|
|
if ( is_off(F, 0) )
|
|
{
|
|
// NB: insn_t::add_dref uses insn.ea to calculate the target
|
|
// of a reloc so we can't use it here
|
|
ea_t newto = get_name_base_ea(ea, target);
|
|
dref_t type = dr_O;
|
|
if ( newto != target )
|
|
{
|
|
type = dref_t(type | XREF_TAIL);
|
|
target = newto;
|
|
}
|
|
add_dref(insn.ea, target, type);
|
|
// helper.altdel_ea(ea, DELAY_TAG);
|
|
}
|
|
else
|
|
{
|
|
// analyze later for a possible offset
|
|
// helper.altset_ea(ea, 1, DELAY_TAG);
|
|
}
|
|
}
|
|
}
|
|
if ( value != NULL )
|
|
{
|
|
switch ( x.dtype )
|
|
{
|
|
case dt_dword:
|
|
*(uint32*)value = get_dword(ea);
|
|
break;
|
|
case dt_word:
|
|
*(uint16*)value = get_word(ea);
|
|
break;
|
|
case dt_byte:
|
|
*(uint8*)value = get_byte(ea);
|
|
break;
|
|
case dt_float:
|
|
*(uint32*)value = 0;
|
|
get_bytes(value, 4, ea);
|
|
break;
|
|
case dt_double:
|
|
*(uint64*)value = 0;
|
|
get_bytes(value, 8, ea);
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Is the current instruction "return"? (conditional or not)
|
|
bool is_arc_return_insn(const insn_t &insn)
|
|
{
|
|
switch ( insn.itype )
|
|
{
|
|
case ARC_j:
|
|
// j blink (A4) or j [blink] (compact) is a return
|
|
return insn.Op1.reg == BLINK;
|
|
case ARC_leave:
|
|
// leave [..,pcl,...] is a return
|
|
return insn.Op1.reglist & REGLIST_PCL;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
static const int rv_arc[] = { R0, R1, R2, R3, R4, R5, R6, R7, -1 };
|
|
|
|
int get_arc_fastcall_regs(const int **regs)
|
|
{
|
|
*regs = rv_arc;
|
|
return qnumber(rv_arc) - 1;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
static void add_argregs(argloc_t *argloc, int r, int nregs, int size, bool force_scattered)
|
|
{
|
|
QASSERT(10306, size > (nregs-1) * 4);
|
|
QASSERT(10307, r + nregs < qnumber(rv_arc));
|
|
if ( force_scattered || nregs >= 2 && size != 8 )
|
|
{
|
|
scattered_aloc_t *scloc = new scattered_aloc_t;
|
|
int off = 0;
|
|
for ( int i = 0; i < nregs; ++i, ++r, off += 4 )
|
|
{
|
|
argpart_t ®loc = scloc->push_back();
|
|
regloc.off = off;
|
|
regloc.set_reg1(rv_arc[r]);
|
|
regloc.size = qmin(size, 4);
|
|
size -= 4;
|
|
}
|
|
argloc->consume_scattered(scloc);
|
|
}
|
|
else if ( size == 8 )
|
|
{
|
|
argloc->set_reg2(rv_arc[r], rv_arc[r+1]);
|
|
}
|
|
else
|
|
{
|
|
argloc->set_reg1(rv_arc[r]);
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
bool calc_arc_retloc(argloc_t *retloc, const tinfo_t &tif, cm_t /*cc*/)
|
|
{
|
|
if ( !tif.is_void() )
|
|
{
|
|
int size = tif.get_size();
|
|
int nregs = (size + 3) / 4;
|
|
if ( nregs >= qnumber(rv_arc) )
|
|
return false;
|
|
add_argregs(retloc, 0, nregs, size, false);
|
|
}
|
|
debug_print_argloc(-1, *retloc, tif);
|
|
return true;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// note: currently we do not support partial allocation (when part of
|
|
// the argument is in a register and another part is on the stack)
|
|
// fixme, but how? we need a means of telling the kernel about partial allocations
|
|
static bool alloc_args(func_type_data_t *fti, int nfixed)
|
|
{
|
|
if ( !calc_arc_retloc(&fti->retloc, fti->rettype, 0 /*fti->get_cc()*/) )
|
|
return false;
|
|
|
|
int r = 0;
|
|
int fr = 0;
|
|
const int NUMREGARGS = 8;
|
|
|
|
// if function returns its value in the memory
|
|
size_t retsize = fti->rettype.get_size();
|
|
if ( retsize != BADSIZE && retsize > 8 && !fti->rettype.is_floating() )
|
|
r++; // R0 is used to point to the result
|
|
|
|
sval_t spoff = 0;
|
|
for ( int i=0; i < fti->size(); i++ )
|
|
{
|
|
size_t size;
|
|
uint32 align;
|
|
funcarg_t &fa = fti->at(i);
|
|
const tinfo_t &type = fa.type;
|
|
if ( type.empty() && i >= nfixed )
|
|
{
|
|
size = fa.argloc.stkoff();
|
|
align = size;
|
|
}
|
|
else
|
|
{
|
|
size = type.get_size(&align);
|
|
}
|
|
if ( size == BADSIZE )
|
|
return false;
|
|
// XXX: does ARC ABI align 64-bit params? so far doesn't look like it
|
|
if ( size == 8 && align > 4 )
|
|
align = 4;
|
|
#ifndef FP_ABI_HARD
|
|
qnotused(fr);
|
|
#else
|
|
// currently we support only soft fpu abi
|
|
// todo: add config option to switch between abis
|
|
if ( (size == 4 || size == 8 || size == 16)
|
|
&& type.is_floating() )
|
|
{
|
|
// use floating point registers
|
|
int fpr;
|
|
switch ( size )
|
|
{
|
|
case 4:
|
|
fpr = S0 + fr;
|
|
fr++;
|
|
break;
|
|
case 8:
|
|
case 16: // we do not have Q.. registers yet
|
|
fr = align_up(fr, 2);
|
|
fpr = D0 + fr/2;
|
|
fr += 2;
|
|
break;
|
|
}
|
|
if ( fr > 16 )
|
|
goto ALLOC_ON_STACK; // no more fpregs
|
|
fa.argloc.set_reg1(fpr);
|
|
debug_print_argloc(i, fa.argloc, fa.type);
|
|
continue;
|
|
}
|
|
#endif
|
|
size = align_up(size, 4);
|
|
// XXX: align regs to even pairs?
|
|
/*if ( align > 4 && r < NUMREGARGS )
|
|
r = align_up(r, 2);*/
|
|
if ( r < NUMREGARGS && size <= 16 )
|
|
{
|
|
int nregs = (size+3) / 4;
|
|
int start_reg = r;
|
|
r += nregs;
|
|
if ( nregs == 1 )
|
|
{
|
|
fa.argloc.set_reg1(rv_arc[start_reg]);
|
|
}
|
|
else if ( r <= NUMREGARGS )
|
|
{
|
|
add_argregs(&fa.argloc, start_reg, nregs, size, false);
|
|
}
|
|
else
|
|
{ // part of the argument is passed on the stack: mixed scattered
|
|
int nr = NUMREGARGS - start_reg;
|
|
add_argregs(&fa.argloc, start_reg, nr, nr * 4, true);
|
|
scattered_aloc_t &scloc = fa.argloc.scattered();
|
|
argpart_t &stkloc = scloc.push_back();
|
|
stkloc.off = nr * 4;
|
|
stkloc.size = size - stkloc.off;
|
|
stkloc.set_stkoff(0);
|
|
spoff += align_up(stkloc.size, 4);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// ALLOC_ON_STACK:
|
|
if ( align > 4 )
|
|
spoff = align_up(spoff, 8);
|
|
fa.argloc.set_stkoff(spoff);
|
|
spoff += size;
|
|
}
|
|
debug_print_argloc(i, fa.argloc, fa.type);
|
|
}
|
|
fti->stkargs = spoff;
|
|
return true;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
bool calc_arc_arglocs(func_type_data_t *fti)
|
|
{
|
|
return alloc_args(fti, fti->size());
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
bool calc_arc_varglocs(
|
|
func_type_data_t *fti,
|
|
regobjs_t * /*regargs*/,
|
|
int nfixed)
|
|
{
|
|
return alloc_args(fti, nfixed);
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// returns:
|
|
// -1: doesn't spoil anything
|
|
// -2: spoils everything
|
|
// >=0: the number of the spoiled register
|
|
int arc_t::spoils(const insn_t &insn, const uint32 *regs, int n) const
|
|
{
|
|
if ( is_call_insn(insn) )
|
|
return -2;
|
|
|
|
for ( int i=0; i < n; i++ )
|
|
if ( spoils(insn, regs[i]) )
|
|
return i;
|
|
|
|
return -1;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
bool arc_t::arc_set_op_type(
|
|
const insn_t &insn,
|
|
const op_t &x,
|
|
const tinfo_t &tif,
|
|
const char *name,
|
|
eavec_t *visited)
|
|
{
|
|
tinfo_t type = tif;
|
|
switch ( x.type )
|
|
{
|
|
case o_imm:
|
|
if ( type.is_ptr()
|
|
&& x.value != 0
|
|
&& !is_defarg(get_flags(insn.ea), x.n) )
|
|
{
|
|
op_plain_offset(insn.ea, x.n, to_ea(insn.cs, 0));
|
|
return true;
|
|
}
|
|
break;
|
|
case o_mem:
|
|
{
|
|
ea_t dea = to_ea(insn.cs, x.addr);
|
|
return apply_once_tinfo_and_name(dea, type, name);
|
|
}
|
|
case o_displ:
|
|
return apply_tinfo_to_stkarg(insn, x, x.addr, type, name);
|
|
case o_reg:
|
|
{
|
|
uint32 r = x.reg;
|
|
func_t *pfn = get_func(insn.ea);
|
|
if ( pfn == NULL )
|
|
return false;
|
|
bool ok;
|
|
bool farref;
|
|
func_item_iterator_t fii;
|
|
insn_t insn1;
|
|
for ( ok=fii.set(pfn, insn.ea);
|
|
ok && (ok=fii.decode_preceding_insn(visited, &farref, &insn1)) != false;
|
|
)
|
|
{
|
|
if ( visited->size() > 4096 )
|
|
break; // decoded enough of it, abandon
|
|
if ( farref )
|
|
continue;
|
|
switch ( insn1.itype )
|
|
{
|
|
case ARC_mov:
|
|
case ARC_ld:
|
|
if ( insn1.Op1.reg != r )
|
|
continue;
|
|
return arc_set_op_type(insn, insn1.Op2, type, name, visited);
|
|
case ARC_add:
|
|
case ARC_sub:
|
|
// SUB R3, R11, #-var_12C
|
|
// ADD R1, SP, #var_1C
|
|
if ( insn1.Op1.reg != r )
|
|
continue;
|
|
if ( (issp(insn1.Op2) /*|| isfp(insn1.Op2)*/ )
|
|
&& insn1.Op3.type != o_void )
|
|
{
|
|
if ( remove_tinfo_pointer(&type, &name) )
|
|
return apply_tinfo_to_stkarg(insn, insn1.Op3, insn1.Op3.value, type, name);
|
|
}
|
|
// no break
|
|
default:
|
|
{
|
|
int code = spoils(insn, &r, 1);
|
|
if ( code == -1 )
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
int arc_t::use_arc_regarg_type(ea_t ea, const funcargvec_t &rargs)
|
|
{
|
|
int idx = -1;
|
|
insn_t insn;
|
|
if ( decode_insn(&insn, ea) )
|
|
{
|
|
qvector<uint32> regs;
|
|
int n = rargs.size();
|
|
regs.resize(n);
|
|
for ( int i=0; i < n; i++ )
|
|
regs[i] = rargs[i].argloc.reg1();
|
|
|
|
idx = spoils(insn, regs.begin(), n);
|
|
if ( idx >= 0 )
|
|
{
|
|
tinfo_t type = rargs[idx].type;
|
|
const char *name = rargs[idx].name.begin();
|
|
switch ( insn.itype )
|
|
{
|
|
|
|
case ARC_add: // add r1, sp, #stkvar
|
|
case ARC_sub: // sub r1, r11, #0x15C
|
|
if ( (issp(insn.Op2) /*|| isfp(insn.Op2)*/)
|
|
&& insn.Op3.type != o_void )
|
|
if ( remove_tinfo_pointer(&type, &name) )
|
|
apply_tinfo_to_stkarg(insn, insn.Op3, insn.Op3.value, type, name);
|
|
break;
|
|
case ARC_mov:
|
|
case ARC_ld:
|
|
{
|
|
eavec_t visited;
|
|
arc_set_op_type(insn, insn.Op2, type, name, &visited);
|
|
}
|
|
break;
|
|
default: // unknown instruction changed the register, stop tracing it
|
|
idx |= REG_SPOIL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
struct arc_argtinfo_helper_t : public argtinfo_helper_t
|
|
{
|
|
arc_t ±
|
|
arc_argtinfo_helper_t(arc_t &_pm) : pm(_pm) {}
|
|
bool idaapi set_op_tinfo(
|
|
const insn_t &insn,
|
|
const op_t &x,
|
|
const tinfo_t &tif,
|
|
const char *name) override
|
|
{
|
|
eavec_t visited;
|
|
return pm.arc_set_op_type(insn, x, tif, name, &visited);
|
|
}
|
|
|
|
// does the current instruction prepare a stack argument?
|
|
bool idaapi is_stkarg_load(const insn_t &insn, int *src, int *dst) override
|
|
{
|
|
if ( insn.itype == ARC_st && is_sp_based(insn, insn.Op2) )
|
|
{
|
|
*src = 0;
|
|
*dst = 1;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool idaapi has_delay_slot(ea_t caller) override
|
|
{
|
|
insn_t insn;
|
|
return decode_insn(&insn, caller) != 0
|
|
&& pm.is_dslot(insn.ea+insn.size, true);
|
|
}
|
|
};
|
|
|
|
//-------------------------------------------------------------------------
|
|
void arc_t::use_arc_arg_types(ea_t ea, func_type_data_t *fti, funcargvec_t *rargs)
|
|
{
|
|
arc_argtinfo_helper_t argtypes_helper(*this);
|
|
argtypes_helper.use_arg_tinfos(ea, fti, rargs);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// does the current instruction end a basic block?
|
|
bool arc_t::is_arc_basic_block_end(
|
|
const insn_t &insn,
|
|
bool call_insn_stops_block)
|
|
{
|
|
// is this a delay slot of a branch?
|
|
if ( is_dslot(insn.ea, false) )
|
|
return true;
|
|
|
|
// do we flow into next instruction?
|
|
if ( !is_flow(get_flags(insn.ea+insn.size)) )
|
|
return true;
|
|
|
|
if ( is_call_insn(insn) )
|
|
return call_insn_stops_block;
|
|
|
|
// are there jump xrefs from here?
|
|
xrefblk_t xb;
|
|
bool has_jumps = false;
|
|
for ( bool ok=xb.first_from(insn.ea, XREF_FAR); ok && xb.iscode; ok=xb.next_from() )
|
|
{
|
|
if ( xb.type == fl_JF || xb.type == fl_JN )
|
|
{
|
|
has_jumps = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( has_jumps )
|
|
{
|
|
// delayed jump does not end a basic block
|
|
return !has_dslot(insn);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
void arc_t::del_insn_info(ea_t ea)
|
|
{
|
|
// delete delay slot info
|
|
// NB: may not clobber cmd here!
|
|
nodeidx_t ndx = ea2node(ea);
|
|
helper.altdel(ndx, DSLOT_TAG);
|
|
if ( is_code(get_flags(ea)) )
|
|
helper.altdel(ndx + get_item_size(ea), DSLOT_TAG);
|
|
del_callee(ea);
|
|
del_dxref(ea);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
///< \param insn (const ::insn_t*) the instruction
|
|
///< \param state (int) autoanalysis phase
|
|
///< 0: creating functions
|
|
///< 1: creating chunks
|
|
///< \return probability 0..100
|
|
int arc_t::arc_may_be_func(const insn_t &insn, int state)
|
|
{
|
|
// don't add millicode thunks as chunks
|
|
if ( state == 1 && is_millicode(insn.ea) )
|
|
return 100;
|
|
return 0;
|
|
}
|
|
|
|
//======================================================================
|
|
// millicode handling
|
|
//======================================================================
|
|
|
|
//----------------------------------------------------------------------
|
|
void arc_t::rename_if_not_set(ea_t ea, const char *name)
|
|
{
|
|
if ( renamed.find(ea) != renamed.end() )
|
|
return;
|
|
|
|
qstring curname;
|
|
if ( get_name(&curname, ea, GN_NOT_DUMMY) > 0
|
|
&& (is_uname(curname.c_str()) || curname.find(name) != qstring::npos) )
|
|
return;
|
|
force_name(ea, name);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
static int match_ac_pop_ld(const insn_t &insn)
|
|
{
|
|
// ld rNN, [sp, #mm]
|
|
// mm = (NN-13)*4
|
|
if ( ( insn.auxpref & aux_amask ) == aux_anone
|
|
&& insn.Op1.type == o_reg
|
|
&& insn.Op2.type == o_displ
|
|
&& insn.Op2.reg == SP
|
|
&& insn.Op2.membase == 0
|
|
&& ( insn.Op2.addr % 4 ) == 0 )
|
|
{
|
|
int reg = insn.Op1.reg;
|
|
if ( reg == 13 + insn.Op2.addr / 4 )
|
|
{
|
|
return reg;
|
|
}
|
|
}
|
|
// ld.ab r13, [sp, r13]
|
|
if ( ( insn.auxpref & aux_amask ) == aux_ab
|
|
&& insn.Op1.is_reg(R13)
|
|
&& insn.Op2.type == o_phrase
|
|
&& insn.Op2.reg == SP
|
|
&& insn.Op2.secreg == R13 )
|
|
{
|
|
return 13;
|
|
}
|
|
// ld.ab blink, [sp, r12]
|
|
if ( ( insn.auxpref & aux_amask ) == aux_ab
|
|
&& insn.Op1.is_reg(BLINK)
|
|
&& insn.Op2.type == o_phrase
|
|
&& insn.Op2.reg == SP
|
|
&& insn.Op2.secreg == R12 )
|
|
{
|
|
return BLINK;
|
|
}
|
|
return -1;
|
|
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
bool arc_t::check_ac_pop_chain(int *regno, ea_t ea)
|
|
{
|
|
// __ac_pop_26:
|
|
// ld gp, [sp, 0x34]
|
|
// __ac_pop_25:
|
|
// ld r25, [sp, 0x30]
|
|
// [..]
|
|
// __ac_pop_14:
|
|
// ld r14, [sp, 4]
|
|
// __ac_pop_13:
|
|
// ld.ab r13, [sp, r13]
|
|
// __ac_pop_blink:
|
|
// ld.ab blink, [sp, r12]
|
|
// j [blink]
|
|
insn_t insn;
|
|
bool ok = false;
|
|
if ( decode_insn(&insn, ea) > 0 )
|
|
{
|
|
int reg = match_ac_pop_ld(insn);
|
|
if ( reg == BLINK )
|
|
{
|
|
// j [blink] should follow
|
|
if ( decode_insn(&insn, insn.ea + insn.size) > 0
|
|
&& insn.itype == ARC_j
|
|
&& insn.Op1.type == o_displ
|
|
&& insn.Op1.reg == BLINK
|
|
&& insn.Op1.addr == 0 )
|
|
{
|
|
ok = true;
|
|
}
|
|
}
|
|
else if ( reg == R13 )
|
|
{
|
|
int r2;
|
|
ok = check_ac_pop_chain(&r2, insn.ea + insn.size) && r2 == BLINK;
|
|
}
|
|
else if ( reg > R13 && reg <= R26 )
|
|
{
|
|
// recurse to the lower addresses
|
|
int r2;
|
|
ok = check_ac_pop_chain(&r2, insn.ea + insn.size) && r2 == reg - 1;
|
|
}
|
|
if ( ok )
|
|
{
|
|
qstring tmp;
|
|
if ( reg == BLINK )
|
|
tmp = "__ac_pop_blink";
|
|
else
|
|
tmp.sprnt("__ac_pop_%d", reg);
|
|
rename_if_not_set(ea, tmp.c_str());
|
|
*regno = reg;
|
|
}
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
static int match_ac_push_st(const insn_t &insn)
|
|
{
|
|
// st.a rN, [sp,-4]
|
|
if ( insn.itype == ARC_st
|
|
&& insn.auxpref == aux_a
|
|
&& insn.Op2.type == o_displ
|
|
&& insn.Op2.reg == SP
|
|
&& insn.Op2.membase == 0
|
|
&& insn.Op2.addr == ea_t(-4) )
|
|
{
|
|
return insn.Op1.reg;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// __ac_mc_va:
|
|
// FC 1C 88 B1 st.a r6, [sp,-4]
|
|
// FC 1C 48 B1 st.a r5, [sp,-4]
|
|
// FC 1C 08 B1 st.a r4, [sp,-4]
|
|
// E1 C3 push r3
|
|
// E1 C2 push r2
|
|
// E1 C1 push r1
|
|
// E1 C0 push r0
|
|
// 07 C0 ld r0, [sp,0x1C]
|
|
// 1C 1C C0 31 st r7, [sp,0x1C]
|
|
// E1 C0 push r0
|
|
// 01 C0 ld r0, [sp,4] | E0 7F j.d [blink]
|
|
// E0 7E j [blink] | 01 C0 ld r0, [sp,4]
|
|
static bool check_ac_mc_va(ea_t ea)
|
|
{
|
|
// version with j.d
|
|
static const uchar sig_d[] =
|
|
{
|
|
0xFC, 0x1C, 0x88, 0xB1, 0xFC, 0x1C, 0x48, 0xB1, 0xFC, 0x1C,
|
|
0x08, 0xB1, 0xE1, 0xC3, 0xE1, 0xC2, 0xE1, 0xC1, 0xE1, 0xC0,
|
|
0x07, 0xC0, 0x1C, 0x1C, 0xC0, 0x31, 0xE1, 0xC0, 0xE0, 0x7F,
|
|
0x01, 0xC0
|
|
};
|
|
|
|
// version with non-delayed j
|
|
static const uchar sig_nd[] =
|
|
{
|
|
0xFC, 0x1C, 0x88, 0xB1, 0xFC, 0x1C, 0x48, 0xB1, 0xFC, 0x1C,
|
|
0x08, 0xB1, 0xE1, 0xC3, 0xE1, 0xC2, 0xE1, 0xC1, 0xE1, 0xC0,
|
|
0x07, 0xC0, 0x1C, 0x1C, 0xC0, 0x31, 0xE1, 0xC0, 0x01, 0xC0,
|
|
0xE0, 0x7E
|
|
};
|
|
|
|
CASSERT(sizeof(sig_d) == sizeof(sig_nd));
|
|
const int patlen = sizeof(sig_d);
|
|
uint8 buf[patlen];
|
|
if ( get_bytes(buf, patlen, ea, GMB_READALL) == patlen )
|
|
{
|
|
return memcmp(buf,sig_d, patlen) == 0
|
|
|| memcmp(buf,sig_nd, patlen) == 0;
|
|
}
|
|
return false;
|
|
|
|
}
|
|
//----------------------------------------------------------------------
|
|
static bool check_ac_push_chain(int *regno, ea_t ea)
|
|
{
|
|
// __ac_push_13_to_26:
|
|
// st.a gp, [sp,-4]
|
|
// __ac_push_13_to_25:
|
|
// st.a r25, [sp,-4]
|
|
// [..]
|
|
// __ac_push_13_to_14:
|
|
// st.a r14, [sp,-4]
|
|
// __ac_push_13_to_13:
|
|
// j.d [blink]
|
|
// st.a r13, [sp,-4]
|
|
// VARIATION:
|
|
// __ac_push_13_to_13:
|
|
// st.a r13, [sp,-4]
|
|
// j [blink]
|
|
|
|
insn_t insn;
|
|
bool ok = false;
|
|
int reg;
|
|
if ( decode_insn(&insn, ea) > 0 )
|
|
{
|
|
if ( insn.itype == ARC_j
|
|
&& insn.auxpref == aux_d
|
|
&& insn.Op1.type == o_displ
|
|
&& insn.Op1.reg == BLINK
|
|
&& insn.Op1.addr == 0 )
|
|
{
|
|
// j.d [blink]
|
|
// must be followed by st.a r13, [sp,-4]
|
|
if ( decode_insn(&insn, insn.ea + insn.size) > 0
|
|
&& match_ac_push_st(insn) == 13 )
|
|
{
|
|
reg = 13;
|
|
ok = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// st.a rN, [sp,-4]
|
|
reg = match_ac_push_st(insn);
|
|
if ( reg == R13 )
|
|
{
|
|
// j [blink] should follow
|
|
if ( decode_insn(&insn, insn.ea + insn.size) > 0
|
|
&& insn.itype == ARC_j
|
|
&& insn.auxpref == 0
|
|
&& insn.Op1.type == o_displ
|
|
&& insn.Op1.reg == BLINK
|
|
&& insn.Op1.addr == 0 )
|
|
{
|
|
ok = true;
|
|
}
|
|
}
|
|
if ( reg > R13 && reg <= R26 )
|
|
{
|
|
// recurse to the lower addresses
|
|
int r2;
|
|
ok = check_ac_push_chain(&r2, insn.ea + insn.size) && r2 == reg - 1;
|
|
}
|
|
}
|
|
if ( ok )
|
|
{
|
|
*regno = reg;
|
|
}
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
bool arc_t::detect_millicode(qstring *mname, ea_t ea)
|
|
{
|
|
// MetaWare arcompact millicode
|
|
// __ac_pop_13_to_26:
|
|
// mov r12, 4
|
|
// __ac_pop_13_to_26v:
|
|
// mov r13, 0x38
|
|
// b __ac_pop_26
|
|
// [...]
|
|
// __ac_pop_13_to_13:
|
|
// mov r12, 4
|
|
// __ac_pop_13_to_13v:
|
|
// mov r13, 4
|
|
// b __ac_pop_13
|
|
// __ac_pop_none:
|
|
// mov r12, 4
|
|
// __ac_pop_nonev:
|
|
// b __ac_pop_blink
|
|
insn_t insn;
|
|
bool ok = false;
|
|
if ( decode_insn(&insn, ea) > 0 )
|
|
{
|
|
if ( insn.itype == ARC_mov )
|
|
{
|
|
if ( insn.Op1.is_reg(R13) && insn.Op2.type == o_imm && (insn.Op2.value % 4) == 0 )
|
|
{
|
|
// mov r13, 0x38
|
|
int regno = 12 + insn.Op2.value / 4;
|
|
if ( decode_insn(&insn, insn.ea + insn.size) > 0 && insn.itype == ARC_b && insn.Op1.type == o_near )
|
|
{
|
|
// b __ac_pop_N
|
|
ea_t dest = insn.Op1.addr;
|
|
int regno2;
|
|
if ( check_ac_pop_chain(®no2, dest) && regno == regno2 )
|
|
{
|
|
mname->sprnt("__ac_pop_13_to_%dv", regno);
|
|
ok = true;
|
|
}
|
|
}
|
|
}
|
|
else if ( insn.Op1.is_reg(R12) && insn.Op2.type == o_imm && insn.Op2.value == 4 )
|
|
{
|
|
// mov r12, 4
|
|
// check for fall through into __ac_pop_13_to_NNv
|
|
if ( detect_millicode(mname, insn.ea + insn.size) && mname->last() == 'v' )
|
|
{
|
|
// erase the last 'v'
|
|
mname->resize(mname->length() - 1);
|
|
ok = true;
|
|
}
|
|
}
|
|
}
|
|
else if ( insn.itype == ARC_b && insn.Op1.type == o_near )
|
|
{
|
|
// b __ac_pop_blink ?
|
|
int regno2;
|
|
if ( check_ac_pop_chain(®no2, insn.Op1.addr) && regno2 == BLINK )
|
|
{
|
|
*mname = "__ac_pop_nonev";
|
|
ok = true;
|
|
}
|
|
}
|
|
else if ( insn.itype == ARC_st )
|
|
{
|
|
int reg;
|
|
if ( check_ac_push_chain(®, ea) )
|
|
{
|
|
mname->sprnt("__ac_push_13_to_%d", reg);
|
|
ok = true;
|
|
}
|
|
else if ( check_ac_mc_va(ea) )
|
|
{
|
|
*mname = "__ac_mc_va";
|
|
ok = true;
|
|
}
|
|
}
|
|
}
|
|
if ( ok )
|
|
{
|
|
rename_if_not_set(ea, mname->c_str());
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
static bool check_millicode_name(const qstring &name, ea_t ea, sval_t *spdelta)
|
|
{
|
|
qstring cname;
|
|
if ( cleanup_name(&cname, ea, name.c_str(), CN_KEEP_TRAILING__DIGITS) )
|
|
{
|
|
const char *p = cname.c_str();
|
|
if ( streq(p, "ac_push_none")
|
|
|| streq(p, "ac_pop_none") )
|
|
{
|
|
*spdelta = 0;
|
|
return true;
|
|
}
|
|
else if ( streq(p, "ac_mc_va") )
|
|
{
|
|
// pushes r0-r7
|
|
*spdelta = -4*8;
|
|
return true;
|
|
}
|
|
else if ( streq(p, "ac_push_nonev") )
|
|
{
|
|
// adjusts sp by r12
|
|
*spdelta = BADADDR;
|
|
return true;
|
|
}
|
|
|
|
#define SKIP_PREFIX(x) (strneq(p, x, strlen(x)) && (p+=strlen(x), true))
|
|
if ( SKIP_PREFIX("ac_push_13_to_") )
|
|
{
|
|
int reg = atoi(p);
|
|
if ( reg >= 13 && reg <= 26 )
|
|
{
|
|
// pushes 13..reg
|
|
*spdelta = -4 * (reg-13+1);
|
|
return true;
|
|
}
|
|
}
|
|
else if ( SKIP_PREFIX("ac_pop_13_to_") )
|
|
{
|
|
char *p2;
|
|
uint64 reg = strtoull(p, &p2, 10);
|
|
if ( reg >= 13 && reg <= 26
|
|
&& ( *p2 == '\0' || *p2 == 'v' || *p2 == '_' ) )
|
|
{
|
|
// pops 13..reg
|
|
*spdelta = 4 * (reg - 12);
|
|
return true;
|
|
}
|
|
}
|
|
else if ( SKIP_PREFIX("prolog_save") )
|
|
{
|
|
char *p2;
|
|
uint64 reg = strtoull(p, &p2, 10);
|
|
if ( ( reg == 0 || reg >= 13 && reg <= 26 )
|
|
&& ( *p2 == '\0' || strneq(p2, "_sub4", 5) || strneq(p2, "sp", 2) || strneq(p2, "sp_sub4", 7) ) )
|
|
{
|
|
// different suffixes affect the fp value but sp remains unchanged
|
|
*spdelta = 0;
|
|
return true;
|
|
}
|
|
}
|
|
else if ( SKIP_PREFIX("epilog_load")
|
|
|| SKIP_PREFIX("epilog_restore") )
|
|
{
|
|
char *p2;
|
|
uint64 reg = strtoull(p, &p2, 10);
|
|
if ( ( reg == 0 || reg >= 13 && reg <= 26 )
|
|
&& ( *p2 == '\0' || strneq(p2, "_add4", 5) ) )
|
|
{
|
|
// sp delta depends on r12 value
|
|
*spdelta = BADADDR;
|
|
return true;
|
|
}
|
|
}
|
|
else if ( SKIP_PREFIX("st_r13_to_r") )
|
|
{
|
|
char *p2;
|
|
uint64 reg = strtoull(p, &p2, 10);
|
|
if ( reg >= 13 && reg <= 26 )
|
|
{
|
|
*spdelta = 0;
|
|
return true;
|
|
}
|
|
}
|
|
else if ( SKIP_PREFIX("ld_r13_to_r") )
|
|
{
|
|
char *p2;
|
|
uint64 reg = strtoull(p, &p2, 10);
|
|
if ( reg >= 13 && reg <= 26 )
|
|
{
|
|
if ( *p2 == '\0' )
|
|
{
|
|
*spdelta = 0;
|
|
return true;
|
|
}
|
|
else if ( strneq(p2, "_ret", 4) )
|
|
{
|
|
// sp delta depends on r12 value
|
|
*spdelta = BADADDR;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
bool arc_t::is_millicode(ea_t ea, sval_t *spdelta)
|
|
{
|
|
// MetaWare arcompact names (N=13..26):
|
|
// __ac_push_13_to_N
|
|
// __ac_push_none
|
|
// __ac_pop_13_to_N
|
|
// __ac_pop_13_to_Nv
|
|
// __ac_pop_none
|
|
// __ac_pop_nonev
|
|
// __ac_mc_va
|
|
// MetaWare ARC4 names (N=0,13..26)
|
|
// __prolog_saveNsp_sub4
|
|
// __prolog_saveN_sub4
|
|
// __prolog_saveN
|
|
// __prolog_saveNsp
|
|
// __epilog_loadN
|
|
// __epilog_restoreN
|
|
// __epilog_loadN_add4
|
|
// __epilog_restoreN_add4
|
|
// __store_va
|
|
// GCC names (N=15..26)
|
|
// __st_r13_to_rN
|
|
// __ld_r13_to_rN
|
|
// N=14..26
|
|
// __ld_r13_to_rN_ret
|
|
bool detected = false;
|
|
qstring name;
|
|
if ( get_name(&name, ea, GN_NOT_DUMMY) > 0 )
|
|
{
|
|
CHECK_NAME:
|
|
sval_t tmp;
|
|
if ( check_millicode_name(name, ea, &tmp) )
|
|
{
|
|
if ( spdelta != NULL )
|
|
*spdelta = tmp;
|
|
return true;
|
|
}
|
|
if ( !detected )
|
|
return false;
|
|
}
|
|
qstring mname;
|
|
if ( detect_millicode(&mname, ea) )
|
|
{
|
|
if ( name.empty() )
|
|
{
|
|
msg("%a: detected millicode thunk %s\n", ea, mname.c_str());
|
|
force_name(ea, mname.c_str());
|
|
}
|
|
else if ( is_uname(name.c_str()) && name.find(mname) == qstring::npos )
|
|
{
|
|
msg("%a: detected millicode thunk %s but current name is %s, rename for better analysis\n", ea, mname.c_str(), name.c_str());
|
|
}
|
|
name = mname;
|
|
detected = true;
|
|
goto CHECK_NAME;
|
|
}
|
|
return false;
|
|
}
|