/* * Interactive disassembler (IDA). * Copyright (c) 1990-99 by Ilfak Guilfanov. * ALL RIGHTS RESERVED. * E-mail: ig@datarescue.com * * */ #include "hppa.hpp" #include #include #include //---------------------------------------------------------------------- // map virtual to physical ea ea_t calc_mem(ea_t ea) { return ea; } //------------------------------------------------------------------------ ea_t hppa_t::get_dp(const insn_t &insn) const { if ( got == BADADDR ) return BADADDR; sel_t delta = get_sreg(insn.ea, DPSEG); if ( delta == BADSEL ) return BADADDR; // calculate the return value // if we don't do it in a separate statement, bcb6 generates // wrong code with __EA64__ ea_t dp = got + delta; return dp; } //------------------------------------------------------------------------- // returns: // -1: doesn't spoil anything // -2: spoils everything // >=0: the number of the register which is spoiled static int spoils(const insn_t &insn, const uint32 *regs, int n) { switch ( insn.itype ) { case HPPA_call: case HPPA_blr: for ( int i=0; i < n; i++ ) if ( regs[i] >= 23 && regs[i] != DP ) // assume the first 8 registers are not spoiled return i; // dp is never spoiled } return get_spoiled_reg(insn, regs, n); } //---------------------------------------------------------------------- static bool find_addil_or_ldil(ea_t ea, uint32 r, ea_t dp, uval_t *pv) { uval_t v; insn_t insn; func_item_iterator_t fii(get_func(ea), ea); while ( fii.decode_prev_insn(&insn) ) { switch ( insn.itype ) { case HPPA_addil: if ( insn.Op3.reg == r ) { if ( insn.Op2.reg == R0 ) { v = insn.Op1.value; RETTRUE: *pv = v; return true; } if ( insn.Op2.reg == DP ) { v = dp + insn.Op1.value; goto RETTRUE; } } continue; case HPPA_ldil: if ( insn.Op2.reg == r ) { v = insn.Op1.value; goto RETTRUE; } case HPPA_copy: if ( insn.Op2.reg == r ) { r = insn.Op1.reg; if ( r == R0 ) { v = 0; goto RETTRUE; } } continue; } if ( spoils(insn, &r, 1) != -1 ) break; } return false; } //---------------------------------------------------------------------- // addil -0x2800, %dp, %r1 // stw %r5, 0x764(%sr0,%r1) ea_t hppa_t::calc_possible_memref(const insn_t &insn, const op_t &x) { ea_t dp = get_dp(insn); if ( dp != BADADDR ) { if ( x.phrase == DP ) { dp += x.addr; } else { int r = x.phrase; uval_t v = x.addr; uval_t v2 = 0; if ( find_addil_or_ldil(insn.ea, r, dp, &v2) ) { dp = v + v2; } else dp = BADADDR; } } return dp; } //------------------------------------------------------------------------ inline bool is_stkreg(int r) { return r == SP; } //------------------------------------------------------------------------ int idaapi is_sp_based(const insn_t &/*insn*/, const op_t &x) { return OP_SP_ADD | (is_stkreg(x.phrase) ? OP_SP_BASED : OP_FP_BASED); } //------------------------------------------------------------------------ // is the register the frame pointer? bool hppa_t::is_frreg(const insn_t &insn, int reg) { if ( reg != 0 ) { func_t *pfn = get_func(insn.ea); if ( pfn != NULL ) { ea_t ea = pfn->start_ea; if ( ea != oldea ) { oldea = ea; oldreg = helper.altval_ea(oldea); } return reg == oldreg; } } return false; } //------------------------------------------------------------------------ inline bool stldwm(const insn_t &insn) { return insn.itype == HPPA_ldo && insn.Op2.reg == SP // ldo .., %sp || (opcode(get_dword(insn.ea)) & 0x13) == 0x13; // st/ldw,m } //------------------------------------------------------------------------ inline void remove_unwanted_typeinfo(const insn_t &insn, int n) { if ( is_defarg(get_flags(insn.ea), n) ) clr_op_type(insn.ea, n); } //------------------------------------------------------------------------ static void process_immediate_number(const insn_t &insn, int n) { set_immd(insn.ea); if ( is_defarg(get_flags(insn.ea), n) ) return; switch ( insn.itype ) { case HPPA_depd: case HPPA_depw: case HPPA_extrd: case HPPA_extrw: case HPPA_hshl: case HPPA_hshladd: case HPPA_hshr: case HPPA_hshradd: case HPPA_shladd: case HPPA_shrpd: case HPPA_shrpw: case HPPA_shrd: case HPPA_shrw: case HPPA_shld: case HPPA_shlw: op_dec(insn.ea, n); break; case HPPA_depdi: case HPPA_depwi: if ( n == 0 ) op_num(insn.ea, n); else op_dec(insn.ea, n); break; case HPPA_popbts: case HPPA_rsm: case HPPA_ssm: op_num(insn.ea, n); break; } } //---------------------------------------------------------------------- enum nullicode_t { NEVER, SKIP, NULLIFY }; static nullicode_t may_skip_next_insn(ea_t ea) { nullicode_t may = NEVER; insn_t insn; if ( decode_insn(&insn, ea) > 0 ) { switch ( insn.itype ) { case HPPA_pmdis: // format 55 case HPPA_spop0: // format 34 case HPPA_spop1: // format 35 case HPPA_spop2: // format 36 case HPPA_spop3: // format 37 case HPPA_copr: // format 38 may = (get_dword(ea) & BIT26) != 0 ? SKIP : NEVER; break; case HPPA_addb: // format 17 case HPPA_addib: case HPPA_cmpb: case HPPA_cmpib: case HPPA_movb: case HPPA_movib: case HPPA_bb: // format 18 case HPPA_be: // format 19 case HPPA_b: // format 20 case HPPA_blr: // format 21 case HPPA_bv: case HPPA_call: // pseudo-op case HPPA_bve: // format 22 case HPPA_ret: // pseudo-op may = ((get_dword(ea) & BIT30) != 0) ? NULLIFY : NEVER; break; default: may = (insn.auxpref & aux_cndc) != 0 ? SKIP : NEVER; break; } } return may; } //---------------------------------------------------------------------- static bool is_cond_branch(uint32 code) { switch ( opcode(code) ) { case 0x20: // cmpb case 0x22: // cmpb case 0x27: // cmpb case 0x2F: // cmpb case 0x28: // addb case 0x2A: // addb case 0x32: // movb case 0x21: // cmpib case 0x23: // cmpib case 0x3B: // cmpib case 0x29: // addib case 0x2B: // addib case 0x33: // movib case 0x30: // bb case 0x31: // bb return true; } return false; } //---------------------------------------------------------------------- static bool is_uncond_branch(uint32 code) { int sub; switch ( opcode(code) ) { case 0x38: // be return true; case 0x3A: sub = (code>>13) & 7; switch ( sub ) { case 0: // b,l case 1: // b,gate case 2: // blr return r06(code) == R0; case 6: // bv/bve return true; } break; case 0x22: // cmpb case 0x23: // cmpib case 0x2F: // cmpb case 0x2A: // addb case 0x2B: // addib return ((code>>13) & 7) == 0; case 0x32: // movb case 0x33: // movib return ((code>>13) & 7) == 4; } return false; } //---------------------------------------------------------------------- static bool is_call_branch(uint32 code) { int sub; switch ( opcode(code) ) { case 0x39: // be,l return true; case 0x3A: sub = (code>>13) & 7; switch ( sub ) { case 0: // b,l case 1: // b,gate case 2: // blr return r06(code) != R0; case 4: // b,l,push case 5: // b,l case 7: // bve,l return true; } break; } return false; } //---------------------------------------------------------------------- static nullicode_t may_be_skipped(ea_t ea) { if ( !is_flow(get_flags(ea)) ) return NEVER; return may_skip_next_insn(ea-4); } //---------------------------------------------------------------------- static ea_t calc_branch_target(ea_t ea) { ea_t res = BADADDR; insn_t insn; if ( decode_insn(&insn, ea) > 0 ) { for ( int i=0; i < UA_MAXOP; i++ ) { if ( insn.ops[i].type == o_near ) { res = insn.ops[i].addr; break; } } } return res; } //---------------------------------------------------------------------- inline bool is_stop(uint32 code, bool include_calls_and_conds) { return is_uncond_branch(code) || include_calls_and_conds && (is_cond_branch(code) || is_call_branch(code)); } //---------------------------------------------------------------------- // does the specified address have a delay slot? static bool has_delay_slot(ea_t ea, bool include_calls_and_conds) { if ( (include_calls_and_conds || may_be_skipped(ea) != SKIP) && calc_branch_target(ea) != ea+4 ) { uint32 code = get_dword(ea); return is_stop(code, include_calls_and_conds) && (code & BIT30) == 0; } return false; } //---------------------------------------------------------------------- // is the current insruction in a delay slot? static bool is_delayed_stop(const insn_t &insn, bool include_calls_and_conds) { uint32 code = get_dword(insn.ea); if ( (code & BIT30) != 0 // ,n && is_stop(code, include_calls_and_conds) && (include_calls_and_conds || may_be_skipped(insn.ea) != SKIP) ) { // seems to be a branch which nullifies the next instruction return true; } if ( !is_flow(get_flags(insn.ea)) ) return false; return has_delay_slot(insn.ea-4, include_calls_and_conds); } //---------------------------------------------------------------------- void hppa_t::add_near_ref(const insn_t &insn, const op_t &x, ea_t ea) { cref_t ftype = fl_JN; if ( is_call_branch(get_dword(insn.ea)) ) ftype = fl_CN; if ( has_insn_feature(insn.itype, CF_CALL) ) { if ( !func_does_return(ea) ) flow = false; ftype = fl_CN; } insn.add_cref(ea, x.offb, ftype); if ( ftype == fl_CN ) auto_apply_type(insn.ea, ea); } //---------------------------------------------------------------------- inline dref_t calc_dref_type(const insn_t &insn, bool isload) { if ( insn.itype == HPPA_ldo ) return dr_O; return isload ? dr_R : dr_W; } //---------------------------------------------------------------------- static bool create_lvar(const insn_t &insn, const op_t &x, uval_t v) { struct lvar_info_t { int delta; const char *name; //lint !e958 padding is required to align members }; static const lvar_info_t linfo[] = { { -4, "prev_sp" }, { -8, "rs_rp" }, // RP'' (relocation stub RP) { -12, "cleanup" }, { -16, "static_link" }, { -20, "cur_rp" }, { -24, "es_rp" }, // RP' (external/stub RP) { -28, "LPT_" }, // (external SR4/LT pointer) { -32, "LPT" }, // (external Data/LT pointer) }; func_t *pfn = get_func(insn.ea); if ( pfn == NULL ) return false; sval_t delta; member_t *mptr = get_stkvar(&delta, insn, x, v); if ( mptr == NULL ) { if ( !insn.create_stkvar(x, v, STKVAR_VALID_SIZE) ) return false; mptr = get_stkvar(&delta, insn, x, v); if ( mptr == NULL ) return false; // should not happen but better check delta -= pfn->argsize; // delta contains real offset from SP for ( size_t i=0; i < qnumber(linfo); i++ ) { if ( delta == linfo[i].delta ) { set_member_name(get_frame(pfn), delta+pfn->argsize, linfo[i].name); break; } } if ( delta <= -0x34 ) // seems to be an argument in the stack { // this means that the current function // has at least 4 register arguments pfn = get_func(insn.ea); while ( pfn->regargqty < 4 ) add_regarg(pfn, R26-pfn->regargqty, tinfo_t(BT_INT), NULL); } } return op_stkvar(insn.ea, x.n); } //---------------------------------------------------------------------- // recognize the following code: // 20 20 08 01 ldil -0x40000000, %r1 // E4 20 E0 08 be,l 4(%sr7,%r1), %sr0, %r31 # C0000004 // followed by: // ldi NNN, %r22 // as a system call number NNN. // return -1 if not found static int get_syscall_number(ea_t ea) { int syscall = -1; if ( get_dword(ea) == 0x20200801 && get_dword(ea+4) == 0xE420E008 ) { insn_t l; decode_insn(&l, ea+8); if ( l.itype == HPPA_ldi && l.Op2.reg == R22 ) syscall = (int)l.Op1.value; } return syscall; } //---------------------------------------------------------------------- void hppa_t::process_operand(const insn_t &insn, const op_t &x, bool isAlt, bool isload) { switch ( x.type ) { case o_reg: /* if ( x.reg == GP && insn.itype != ALPHA_lda && insn.itype != ALPHA_ldah && insn.itype != ALPHA_br && !isload ) split_srarea(insn.ea+insn.size, GPSEG, BADSEL, SR_auto);*/ break; default: if ( insn.itype == HPPA_fcmp || insn.itype == HPPA_b || insn.itype == HPPA_ftest ) { return; } interr(insn, "emu"); break; case o_based: // (%r5) break; case o_imm: process_immediate_number(insn, x.n); if ( op_adds_xrefs(get_flags(insn.ea), x.n) ) insn.add_off_drefs(x, dr_O, OOF_SIGNED); break; case o_displ: process_immediate_number(insn, x.n); if ( is_stkreg(x.reg) || is_frreg(insn, x.reg) ) { if ( may_create_stkvars() && !is_defarg(get_flags(insn.ea), x.n) ) { if ( stldwm(insn) ) op_num(insn.ea, -1); else create_lvar(insn, x, x.addr); } } else { ea_t ea = calc_possible_memref(insn, x); if ( ea != BADADDR ) { if ( insn.itype == HPPA_be ) add_near_ref(insn, x, ea); else insn.add_dref(ea, x.offb, calc_dref_type(insn, isload)); insn.create_op_data(ea, x); if ( isload ) { ea_t ea2 = get_dword(ea); if ( is_mapped(ea2) ) insn.add_dref(ea2, x.offb, dr_O); } } } // no break case o_phrase: if ( isAlt ) break; if ( op_adds_xrefs(get_flags(insn.ea), x.n) ) { ea_t ea = insn.add_off_drefs(x, isload ? dr_R : dr_W, OOF_SIGNED|OOF_ADDR); if ( ea != BADADDR ) insn.create_op_data(ea, x); } break; case o_near: add_near_ref(insn, x, calc_mem(x.addr)); break; } } //---------------------------------------------------------------------- static bool add_stkpnt(const insn_t &insn, sval_t delta) { func_t *pfn = get_func(insn.ea); if ( pfn == NULL ) return false; return add_auto_stkpnt(pfn, insn.ea+insn.size, delta); } //---------------------------------------------------------------------- void hppa_t::trace_sp(const insn_t &insn) { switch ( insn.itype ) { // stw,m %r3, 0x80(%sr0,%sp) case HPPA_stw: if ( opcode(get_dword(insn.ea)) == 0x1B // stw,m && is_stkreg(insn.Op2.reg) ) { add_stkpnt(insn, insn.Op2.addr); } break; // ldw,m -0x80(%sr0,%sp), %r3 case HPPA_ldw: if ( opcode(get_dword(insn.ea)) == 0x13 // ldw,m && is_stkreg(insn.Op1.reg) ) { add_stkpnt(insn, insn.Op1.addr); } break; case HPPA_ldo: if ( is_stkreg(insn.Op2.reg) ) { // ldo -0x80(%sp), %sp if ( is_stkreg(insn.Op1.reg) ) { add_stkpnt(insn, insn.Op1.addr); } else if ( is_frreg(insn, insn.Op1.reg) ) { // analog of the 'leave' instruction // (restores the original value of sp + optional delta // using the frame pointer register) // ldo 4(%r4), %sp func_t *pfn = get_func(insn.ea); if ( pfn != NULL ) { sval_t delta = insn.Op1.addr + pfn->frregs - get_spd(pfn,insn.ea); add_stkpnt(insn, -delta); } } } break; } } //---------------------------------------------------------------------- int hppa_t::emu(const insn_t &insn) { if ( segtype(insn.ea) == SEG_XTRN ) return 1; uint32 Feature = insn.get_canon_feature(ph); flow = ((Feature & CF_STOP) == 0); int i; for ( i=0; i < PROC_MAXOP; i++ ) if ( has_cf_use(Feature, i) ) process_operand(insn, insn.ops[i], is_forced_operand(insn.ea, i), true); for ( i=0; i < PROC_MAXOP; i++ ) if ( has_cf_chg(Feature, i) ) process_operand(insn, insn.ops[i], is_forced_operand(insn.ea, i), false); // // Determine if the next instruction should be executed // if ( Feature & CF_STOP ) flow = false; if ( is_delayed_stop(insn, false) ) flow = false; if ( flow ) add_cref(insn.ea, insn.ea+insn.size,fl_F); // // Handle SP modifications // if ( may_trace_sp() ) { if ( !flow ) recalc_spd(insn.ea); // recalculate SP register for the next insn else trace_sp(insn); } // Handle system calls if ( insn.itype == HPPA_ldi && !has_cmt(get_flags(insn.ea)) ) { int syscall = get_syscall_number(insn.ea-8); if ( syscall >= 0 ) { const char *syscall_name = get_syscall_name(syscall); if ( syscall_name != NULL ) { append_cmt(insn.ea, syscall_name, false); flags_t F = get_flags(insn.ea-8); if ( has_xref(F) && !has_user_name(F) ) set_name(insn.ea-8, syscall_name, SN_NOCHECK|SN_NOWARN|SN_NODUMMY); } } } return 1; } //---------------------------------------------------------------------- int may_be_func(const insn_t &/*insn*/) // can a function start here? // returns: probability 0..100 { // ldah $gp, 0x2000($27) // if ( insn.itype == ALPHA_ldah && insn.Op1.reg == GP ) // return 100; return 0; } //---------------------------------------------------------------------- int is_sane_insn(const insn_t &insn, int /*nocrefs*/) { // disallow jumps to nowhere if ( insn.Op1.type == o_near && !is_mapped(calc_mem(insn.Op1.addr)) ) return 0; // don't disassemble 0 as break 0,0 automatically if ( insn.itype == HPPA_break && get_dword(insn.ea) == 0 ) return 0; return 1; } //---------------------------------------------------------------------- int idaapi is_align_insn(ea_t ea) { insn_t insn; if ( decode_insn(&insn, ea) < 1 ) return 0; switch ( insn.itype ) { case HPPA_break: if ( get_dword(insn.ea) ) return 0; break; case HPPA_nop: break; default: return 0; } return insn.size; } //---------------------------------------------------------------------- int idaapi hppa_get_frame_retsize(const func_t *) { return 0; // ALPHA doesn't use stack for function return addresses } //---------------------------------------------------------------------- //lint -e{818} could be declared as pointing to const bool hppa_t::create_func_frame(func_t *pfn) { ea_t ea = pfn->start_ea; int frame_reg = 0; for ( int i=0; i < 16; i++ ) { insn_t insn; decode_insn(&insn, ea); if ( insn.itype == HPPA_copy && is_stkreg(insn.Op1.reg) ) frame_reg = insn.Op2.reg; if ( opcode(get_dword(ea)) == 0x1B ) // stw,m { if ( frame_reg != 0 ) helper.altset_ea(pfn->start_ea, frame_reg); // return true;//add_frame(pfn, 0, 0, 0); return add_frame(pfn, insn.Op2.addr, 0, 0); } ea += 4; } return 0; } //---------------------------------------------------------------------- bool is_basic_block_end(const insn_t &insn) { if ( is_delayed_stop(insn, true) ) return true; return !is_flow(get_flags(insn.ea+insn.size)); } //------------------------------------------------------------------------- bool calc_hppa_arglocs(func_type_data_t *fti) { int r = 0; int n = fti->size(); for ( int i=0; i < n; i++ ) { funcarg_t &fa = fti->at(i); size_t a = fa.type.get_size(); if ( a == BADSIZE ) return false; a = align_up(a, inf_get_cc_size_i()); if ( r < 4 ) // first 4 arguments are in %r26, 25, 24, 23 fa.argloc.set_reg1(26 - r); else fa.argloc.set_stkoff(0x24 + r * 4); r += int(a / 4); } fti->stkargs = r < 4 ? 0 : 0x24 + r * 4; return true; } //------------------------------------------------------------------------- static bool hppa_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) ) { return op_plain_offset(insn.ea, x.n, 0); } break; case o_mem: { ea_t dea = calc_mem(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 l; for ( ok=fii.set(pfn, insn.ea); ok && (ok=fii.decode_preceding_insn(&visited, &farref, &l)) != false; ) { if ( visited.size() > 4096 ) break; // decoded enough of it, abandon if ( farref ) continue; switch ( l.itype ) { case HPPA_ldo: if ( l.Op2.reg != r ) continue; remove_tinfo_pointer(&type, &name); // no break case HPPA_copy: case HPPA_ldw: case HPPA_ldi: case HPPA_ldil: if ( l.Op2.reg != r ) continue; return hppa_set_op_type(l, l.Op1, type, name, visited); default: { int code = spoils(insn, &r, 1); if ( code == -1 ) continue; } break; } break; } if ( !ok && l.ea == pfn->start_ea ) { // reached the function start, this looks like a register argument add_regarg(pfn, r, type, name); break; } } break; } return false; } //------------------------------------------------------------------------- inline bool set_op_type( const insn_t &insn, const op_t &x, const tinfo_t &type, const char *name) { eavec_t visited; return hppa_set_op_type(insn, x, type, name, visited); } //------------------------------------------------------------------------- int use_hppa_regarg_type(ea_t ea, const funcargvec_t &rargs) { insn_t insn; int idx = -1; if ( decode_insn(&insn, ea) > 0 ) { qvector 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 HPPA_ldo: remove_tinfo_pointer(&type, &name); // no break case HPPA_copy: case HPPA_ldw: case HPPA_ldi: case HPPA_ldil: set_op_type(insn, insn.Op1, type, name); case HPPA_depw: case HPPA_depwi: case HPPA_depd: case HPPA_depdi: case HPPA_extrw: case HPPA_extrd: break; default: // unknown instruction changed the register, stop tracing it idx |= REG_SPOIL; break; } } } return idx; } //------------------------------------------------------------------------- struct hppa_argtinfo_helper_t : public argtinfo_helper_t { bool idaapi set_op_tinfo( const insn_t &_insn, const op_t &x, const tinfo_t &tif, const char *name) override { return set_op_type(_insn, x, tif, name); } // 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 == HPPA_stw ) { *src = 0; *dst = 1; return true; } return false; } bool idaapi has_delay_slot(ea_t caller) override { return ::has_delay_slot(caller, true); } }; //------------------------------------------------------------------------- void hppa_t::use_hppa_arg_types(ea_t ea, func_type_data_t *fti, funcargvec_t *rargs) { hppa_argtinfo_helper_t argtypes_helper; argtypes_helper.use_arg_tinfos(ea, fti, rargs); }