Files
2021-10-31 21:20:46 +02:00

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 &pm;
// 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 &pm;
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 &regloc = 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 &pm;
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(&regno2, 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(&regno2, insn.Op1.addr) && regno2 == BLINK )
{
*mname = "__ac_pop_nonev";
ok = true;
}
}
else if ( insn.itype == ARC_st )
{
int reg;
if ( check_ac_push_chain(&reg, 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;
}