#include "st9.hpp" //---------------------------------------------------------------------- static sel_t calc_page(ea_t insn_ea, ushort addr) { return get_sreg(insn_ea, rDPR0+(addr>>14)); } //---------------------------------------------------------------------- static ea_t calc_data_mem_without_mapping(ea_t insn_ea, ea_t addr) { sel_t page = calc_page(insn_ea, (ushort)addr); if ( page == BADSEL ) return BADADDR; ea_t ea = use_mapping((page<<14) + (addr & 0x3FFF)); return ea; } //---------------------------------------------------------------------- ea_t get_dest_addr(const insn_t &insn, const op_t &x) { if ( x.type == o_far ) return x.addr; else if ( x.type == o_mem ) return calc_data_mem_without_mapping(insn.ea, x.addr); else if ( x.type == o_near ) return to_ea(insn.cs, x.addr); else return BADADDR; } //---------------------------------------------------------------------- // Emulate an operand. void st9_t::handle_operand(const insn_t &insn, const op_t &op, bool lwrite) { switch ( op.type ) { // Code address case o_near: case o_far: { cref_t mode; ea_t ea = get_dest_addr(insn, op); // call or jump ? if ( is_call_insn(insn) ) { if ( !func_does_return(ea) ) flow = false; mode = op.type == o_near ? fl_CN: fl_CF; } else { mode = op.type == o_near ? fl_JN: fl_JF; } insn.add_cref(ea, op.offb, mode); } break; // Memory address case o_mem: { ea_t ea = get_dest_addr(insn, op); insn.add_dref(ea, op.offb, lwrite ? dr_W : dr_R); insn.create_op_data(ea, op); } break; // Immediate value case o_imm: { set_immd(insn.ea); flags_t F = get_flags(insn.ea); // create a comment if this immediate is represented in the .cfg file { const ioport_t * port = find_sym(op.value); if ( port != NULL && !has_cmt(F) ) set_cmt(insn.ea, port->cmt.c_str(), false); } // if the value was converted to an offset, then create a data xref: if ( op_adds_xrefs(F, op.n) ) insn.add_off_drefs(op, dr_O, 0); } break; // Displacement case o_displ: { set_immd(insn.ea); flags_t F = get_flags(insn.ea); if ( op_adds_xrefs(F, op.n) ) { ea_t ea = insn.add_off_drefs(op, dr_O, OOF_ADDR); insn.create_op_data(ea, op); } // create stack variables if required if ( may_create_stkvars() && !is_defarg(F, op.n) && op.reg == rrr14 ) { func_t *pfn = get_func(insn.ea); if ( pfn != NULL && pfn->flags & FUNC_FRAME ) { adiff_t displ = (int16)op.addr; if ( insn.create_stkvar(op, displ, STKVAR_VALID_SIZE) ) { op_stkvar(insn.ea, op.n); if ( insn.Op2.type == o_reg ) { regvar_t *r = find_regvar(pfn, insn.ea, ph.reg_names[insn.Op2.reg]); if ( r != NULL ) { struc_t *s = get_frame(pfn); member_t *m = get_stkvar(NULL, insn, op, displ); if ( s != NULL && m != NULL ) { char b[20]; qsnprintf(b, sizeof b, "%scopy", r->user); set_member_name(s, m->soff, b); } } } } } } } break; // Register - Phrase - Void: do nothing case o_reg: case o_phrase: case o_void: break; default: INTERR(10076); } } //---------------------------------------------------------------------- // Emulate an instruction. int st9_t::st9_emu(const insn_t &insn) { uint32 feature = insn.get_canon_feature(ph); flow = ((feature & CF_STOP) == 0); // is it "jump always"? if ( is_jmp_cc(insn.itype) && insn.auxpref == cT ) flow = false; if ( insn.Op1.type != o_void) handle_operand(insn, insn.Op1, (feature & CF_CHG1) != 0); if ( insn.Op2.type != o_void) handle_operand(insn, insn.Op2, (feature & CF_CHG2) != 0); if ( insn.Op3.type != o_void) handle_operand(insn, insn.Op3, (feature & CF_CHG3) != 0); if ( flow ) add_cref(insn.ea, insn.ea + insn.size, fl_F); // Following code will update the current value of the two virtual // segment registers: RW (register window) and RP (register page). bool rw_has_changed = false; bool rp_has_changed = false; switch ( insn.itype ) { case st9_srp: { sel_t val = insn.Op1.value; if ( val % 2 ) val--; // even reduced split_sreg_range(insn.ea+insn.size, rRW, val | (val << 8), SR_auto); } rw_has_changed = true; break; case st9_srp0: { sel_t RW = get_sreg(insn.ea, rRW); split_sreg_range(insn.ea+insn.size, rRW, insn.Op1.value | (RW & 0xFF00), SR_auto); } rw_has_changed = true; break; case st9_srp1: { sel_t RW = get_sreg(insn.ea, rRW); split_sreg_range(insn.ea+insn.size, rRW, (insn.Op1.value << 8) | (RW & 0x00FF), SR_auto); } rw_has_changed = true; break; case st9_spp: split_sreg_range(insn.ea+insn.size, rRP, insn.Op1.value, SR_auto); rp_has_changed = true; break; } // If RW / RP registers have changed, print a comment which explains the new mapping of // the general registers. flags_t F = get_flags(insn.ea); if ( rw_has_changed && !has_cmt(F) ) { char buf[MAXSTR]; sel_t RW = get_sreg(insn.ea+insn.size, rRW); int low = RW & 0x00FF; int high = (RW & 0xFF00) >> 8; low *= 8; high *= 8; const char *const fmt = "r0 -> R%d, r1 -> R%d, r2 -> R%d, r3 -> R%d, r4 -> R%d, r5 -> R%d, r6 -> R%d, r7 -> R%d,\n" "r8 -> R%d, r9 -> R%d, r10 -> R%d, r11 -> R%d, r12 -> R%d, r13 -> R%d, r14 -> R%d, r15 -> R%d"; qsnprintf(buf, sizeof buf, fmt, 0 + low, 1 + low, 2 + low, 3 + low, 4 + low, 5 + low, 6 + low, 7 + low, 8 + high, 9 + high, 10 + high, 11 + high, 12 + high, 13 + high, 14 + high, 15 + high); set_cmt(insn.ea, buf, false); } if ( rp_has_changed && !has_cmt(F) ) { char buf[MAXSTR]; int rpval = get_sreg(insn.ea+insn.size, rRP); qsnprintf(buf, sizeof buf, "Registers R240-R255 will now be referred to the page %d of paged registers", rpval); set_cmt(insn.ea, buf, false); } // reanalyze switch info if ( insn.itype == st9_jp && get_auto_state() == AU_USED ) { switch_info_t si; if ( get_switch_info(&si, insn.ea) > 0 && !si.is_user_defined() ) { delete_switch_table(insn.ea, si); if ( st9_is_switch(&si, insn) ) { set_switch_info(insn.ea, si); create_switch_table(insn.ea, si); } else { del_switch_info(insn.ea); } } } return 1; } //---------------------------------------------------------------------- // Analyze an instruction static ea_t next_insn(insn_t *insn, ea_t ea) { if ( decode_insn(insn, ea) == 0 ) return 0; ea += insn->size; return ea; } //------------------------------------------------------------------------ // does a far return instruction precede 'ea'? static bool is_far_return(ea_t ea) { insn_t insn; if ( decode_prev_insn(&insn, ea) != BADADDR ) return insn.itype == st9_rets; return false; } //---------------------------------------------------------------------- // if a function ends with a far return, mark it as such // NB: we only handle regular (non-chunked) functions static void setup_far_func(func_t *pfn) { if ( (pfn->flags & FUNC_FAR) == 0 ) { if ( is_far_return(pfn->end_ea) ) { pfn->flags |= FUNC_FAR; update_func(pfn); } } } //---------------------------------------------------------------------- // Create a function frame bool st9_t::create_func_frame(func_t *pfn) const { setup_far_func(pfn); ea_t ea = pfn->start_ea; insn_t insn; ea = next_insn(&insn, ea); if ( !ea ) return 0; /* * Get the total frame size * * LINK rr14, #size */ if ( insn.itype != st9_link ) return 0; int link_register = insn.Op1.reg; size_t total_size = (size_t)insn.Op2.value; /* * Get arguments size * * LDW 0x??(rr14), RR??? a word * LD '' a byte */ int args_size = 0; for ( int i = 0; true; i++ ) { insn_t ldi; ea = next_insn(&ldi, ea); if ( !ea ) return 0; if ( ldi.Op1.type != o_displ || ldi.Op2.type != o_reg ) break; if ( ldi.Op1.reg != link_register ) break; if ( ldi.itype == st9_ld ) // byte args_size++; else if ( ldi.itype == st9_ldw ) // word args_size += 2; else break; char regvar[10]; qsnprintf(regvar, sizeof regvar, "arg_%d", i); int err = add_regvar(pfn, ldi.ea, ldi.ea + ldi.size, ph.reg_names[ldi.Op2.reg], regvar, NULL); if ( err ) msg("add_regvar() failed : error %d\n", err); } //msg("LOCAL: %d\nARGS: %d\n", total_size - args_size, args_size); pfn->flags |= FUNC_FRAME; return add_frame(pfn, total_size - args_size, 0, args_size); } //------------------------------------------------------------------------ /* GCC?-produced switch: ldw ridx, rin [optional] cpw rin, #n jpugt default | jrugt default addw ridx, ridx spm ldw rjmp, jtbl(ridx) sdm jp (rjmp) jtbl: .word case0, case1, ... */ static ea_t check_prev_insn(int itype, insn_t &insn) { ea_t ea = decode_prev_insn(&insn, insn.ea); if ( ea == BADADDR || insn.itype != itype ) return BADADDR; return ea; } //-------------------------------------------------------------------------- static bool is_gcc_switch(switch_info_t *_si, insn_t &insn) { switch_info_t &si = *_si; int rjmp, ridx; // si.flags |= SWI_J32; ea_t ea, jtbl_insn; // // Check jump insn and get register number // jp (rjmp) if ( insn.itype != st9_jp || insn.Op1.type != o_reg || !is_ind(insn.Op1) ) { return false; } rjmp = insn.Op1.reg; // sdm ea = check_prev_insn(st9_sdm, insn); if ( ea == BADADDR ) return false; // ldw rjmp, jtbl(ridx) ea = check_prev_insn(st9_ldw, insn); if ( ea == BADADDR || !insn.Op1.is_reg(rjmp) || insn.Op2.type != o_displ ) return false; ridx = insn.Op2.reg; jtbl_insn = ea; // this addr is offset in current code segment because of spm si.jumps = to_ea(insn.cs, insn.Op2.addr); // spm ea = check_prev_insn(st9_spm, insn); if ( ea == BADADDR ) return false; // addw ridx, ridx ea = check_prev_insn(st9_addw, insn); if ( ea == BADADDR || !insn.Op1.is_reg(ridx) || !insn.Op2.is_reg(ridx) ) return false; // jpugt default | jrugt default ea = decode_prev_insn(&insn, ea); if ( ea != BADADDR && is_jmp_cc(insn.itype) && insn.auxpref == cUGT ) { si.defjump = get_dest_addr(insn, insn.Op1); // cpw rin, #n ea = check_prev_insn(st9_cpw, insn); if ( ea == BADADDR || insn.Op2.type != o_imm ) return false; int rin = insn.Op1.reg; si.ncases = ushort(insn.Op2.value+1); // is rin the same as ridx? bool ok = insn.Op1.is_reg(ridx); if ( !ok ) { // check for preceding ldw ridx, rin ea_t ea2 = decode_prev_insn(&insn, ea); if ( ea2 != BADADDR && insn.itype == st9_ldw && insn.Op1.is_reg(ridx) && insn.Op2.is_reg(rin) ) ok = true; } if ( !ok ) return false; si.set_expr(rin, insn.Op1.dtype); } // // Everything ok. // msg("SWITCH %a: gcc_switch\n", insn.ea); si.startea = ea; si.set_jtable_element_size(2); si.set_shift(0); op_num(ea, 1); // cpw rin, #n op_plain_offset(jtbl_insn, 1, to_ea(insn.cs, 0)); // ldw rjmp, jtbl(ridx) return true; } bool st9_is_switch(switch_info_t *si, const insn_t &insn) { if ( insn.itype == st9_jp ) { insn_t copy = insn; return is_gcc_switch(si, copy); } return false; }