490 lines
12 KiB
C++
490 lines
12 KiB
C++
|
|
#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;
|
|
}
|
|
|
|
|