1409 lines
57 KiB
Python
1409 lines
57 KiB
Python
# ----------------------------------------------------------------------
|
|
# Texas Instruments MSP430 processor module
|
|
# Copyright (c) 2010-2021 Hex-Rays
|
|
#
|
|
# This module demonstrates:
|
|
# - instruction decoding and printing
|
|
# - simplification of decoded instructions
|
|
# - creation of code and data cross-references
|
|
# - auto-creation of data items from cross-references
|
|
# - tracing of the stack pointer changes
|
|
# - creation of the stack variables
|
|
# - handling of switch constructs
|
|
#
|
|
# Please send fixes or improvements to support@hex-rays.com
|
|
|
|
import sys
|
|
import copy
|
|
|
|
from ida_bytes import *
|
|
from ida_ua import *
|
|
from ida_idp import *
|
|
from ida_auto import *
|
|
from ida_nalt import *
|
|
from ida_funcs import *
|
|
from ida_lines import *
|
|
from ida_problems import *
|
|
from ida_offset import *
|
|
from ida_segment import *
|
|
from ida_name import *
|
|
from ida_netnode import *
|
|
from ida_xref import *
|
|
from ida_idaapi import *
|
|
import ida_frame
|
|
import idc
|
|
|
|
if sys.version_info.major < 3:
|
|
range = xrange
|
|
|
|
# extract bitfield occupying bits high..low from val (inclusive, start from 0)
|
|
def BITS(val, high, low):
|
|
return (val>>low)&((1<<(high-low+1))-1)
|
|
|
|
# extract one bit
|
|
def BIT(val, bit):
|
|
return (val>>bit) & 1
|
|
|
|
# sign extend b low bits in x
|
|
# from "Bit Twiddling Hacks"
|
|
def SIGNEXT(x, b):
|
|
m = 1 << (b - 1)
|
|
x = x & ((1 << b) - 1)
|
|
return (x ^ m) - m
|
|
|
|
# values for specval field for o_phrase operands
|
|
FL_INDIRECT = 1 # indirect: @Rn
|
|
FL_AUTOINC = 2 # auto-increment: @Rn+
|
|
|
|
# values for specval field for o_mem operands
|
|
FL_ABSOLUTE = 1 # absolute: &addr
|
|
FL_SYMBOLIC = 2 # symbolic: addr
|
|
|
|
# values for insn_t.auxpref
|
|
AUX_SIZEMASK = 0x0F
|
|
AUX_NOSUF = 0x00 # no suffix (e.g. SWPB)
|
|
AUX_WORD = 0x01 # word transfer, .W suffix
|
|
AUX_BYTE = 0x02 # byte transfer, .B suffix
|
|
AUX_A = 0x03 # 20-bit transfer, .A suffix
|
|
AUX_AX = 0x04 # 20-bit immediate/address, no suffix
|
|
AUX_REPIMM = 0x10 # immediate repeat count present (in insn_t.segpref)
|
|
AUX_REPREG = 0x20 # register repeat count (reg no in in insn_t.segpref)
|
|
AUX_ZC = 0x40 # zero carry flag is set
|
|
|
|
# addressing mode field in the opcode
|
|
AM_REGISTER = 0 # Rn
|
|
AM_INDEXED = 1 # X(Rn), also includes symbolic and absolute
|
|
AM_INDIRECT = 2 # @Rn
|
|
AM_AUTOINC = 3 # @Rn+, also includes immediate (#N = @PC+)
|
|
# extra formats
|
|
AM_IMM20 = 100 # Rn is imm19:16, imm15:0 follows
|
|
AM_ABS20 = 101 # Rn is &abs19:16, &abs15:0 follows
|
|
AM_SYM20 = 102 # as IMM20, plus PC value
|
|
|
|
# operand data length value
|
|
# A/L B/W
|
|
DLEN_WORD = 0 # 1 0 16-bit word
|
|
DLEN_BYTE = 1 # 1 1 8-bit byte
|
|
DLEN_AWORD = 2 # 0 1 20-bit address-word
|
|
DLEN_LONG = 3 # 0 0 Reserved
|
|
|
|
# check if operand is immediate value val
|
|
def is_imm_op(op, val):
|
|
if op.type == o_imm:
|
|
# workaround for difference between Python and native numbers
|
|
op2 = op_t()
|
|
op2.value = val
|
|
return op.value == op2.value
|
|
return False
|
|
|
|
# are operands equal?
|
|
def same_op(op1, op2):
|
|
return op1.type == op2.type and \
|
|
op1.reg == op2.reg and \
|
|
op1.value == op2.value and \
|
|
op1.addr == op2.addr and \
|
|
op1.flags == op2.flags and \
|
|
op1.specval == op2.specval and \
|
|
op1.dtype == op2.dtype
|
|
|
|
# is operand auto-increment register reg?
|
|
def is_autoinc(op, reg):
|
|
return op.type == o_phrase and op.reg == reg and op.specval == FL_AUTOINC
|
|
|
|
# is sp delta fixed by the user?
|
|
def is_fixed_spd(ea):
|
|
return (get_aflags(ea) & AFL_FIXEDSPD) != 0
|
|
|
|
# ----------------------------------------------------------------------
|
|
class msp430_processor_t(processor_t):
|
|
"""
|
|
Processor module classes must derive from processor_t
|
|
"""
|
|
|
|
# IDP id ( Numbers above 0x8000 are reserved for the third-party modules)
|
|
id = PLFM_MSP430
|
|
|
|
# Processor features
|
|
flag = PR_SEGS | PRN_HEX | PR_RNAMESOK | PR_WORD_INS \
|
|
| PR_USE32 | PR_DEFSEG32
|
|
|
|
# Number of bits in a byte for code segments (usually 8)
|
|
# IDA supports values up to 32 bits (64 for IDA64)
|
|
cnbits = 8
|
|
|
|
# Number of bits in a byte for non-code segments (usually 8)
|
|
# IDA supports values up to 32 bits (64 for IDA64)
|
|
dnbits = 8
|
|
|
|
# short processor names
|
|
# Each name should be shorter than 9 characters
|
|
psnames = ['msp430']
|
|
|
|
# long processor names
|
|
# No restriction on name lengthes.
|
|
plnames = ['Texas Instruments MSP430']
|
|
|
|
# size of a segment register in bytes
|
|
segreg_size = 0
|
|
|
|
# Array of typical code start sequences (optional)
|
|
codestart = ['\x0B\x12'] # 120B: push R11
|
|
|
|
# Array of 'return' instruction opcodes (optional)
|
|
# retcodes = ['\x30\x41'] # 4130: ret (mov.w @SP+, PC)
|
|
|
|
# Array of instructions
|
|
instruc = [
|
|
{'name': '', 'feature': 0}, # placeholder for "not an instruction"
|
|
|
|
# two-operand instructions
|
|
{'name': 'mov', 'feature': CF_USE1 | CF_CHG2, 'cmt': "Move source to destination"},
|
|
{'name': 'add', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Add source to destination"},
|
|
{'name': 'addc', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Add source and carry to destination"},
|
|
{'name': 'subc', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Subtract source with carry from destination"},
|
|
{'name': 'sub', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Subtract source from destination"},
|
|
{'name': 'cmp', 'feature': CF_USE1 | CF_USE2, 'cmt': "Compare source and destination"},
|
|
{'name': 'dadd', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Add source decimally to destination"},
|
|
{'name': 'bit', 'feature': CF_USE1 | CF_USE2, 'cmt': "Test bits set in source in destination"},
|
|
{'name': 'bic', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Clear bits set in source in destination"},
|
|
{'name': 'bis', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Set bits set in source in destination"},
|
|
{'name': 'xor', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Exclusive OR source with destination"},
|
|
{'name': 'and', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Binary AND source and destination"},
|
|
|
|
# MSP430X instructions
|
|
{'name': 'movx', 'feature': CF_USE1 | CF_CHG2, 'cmt': "Move source to destination"},
|
|
{'name': 'addx', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Add source to destination"},
|
|
{'name': 'addcx', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Add source and carry to destination"},
|
|
{'name': 'subcx', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Subtract source with carry from destination"},
|
|
{'name': 'subx', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Subtract source from destination"},
|
|
{'name': 'cmpx', 'feature': CF_USE1 | CF_USE2, 'cmt': "Compare source and destination"},
|
|
{'name': 'daddx', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Add source decimally to destination"},
|
|
{'name': 'bitx', 'feature': CF_USE1 | CF_USE2, 'cmt': "Test bits set in source in destination"},
|
|
{'name': 'bicx', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Clear bits set in source in destination"},
|
|
{'name': 'bisx', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Set bits set in source in destination"},
|
|
{'name': 'xorx', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Exclusive OR source with destination"},
|
|
{'name': 'andx', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Binary AND source and destination"},
|
|
{'name': 'rrcm', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Rotate right through C"},
|
|
{'name': 'rram', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Rotate right arithmetically"},
|
|
{'name': 'rlam', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Rotate left arithmetically"},
|
|
{'name': 'rrum', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Rotate right unsigned"},
|
|
{'name': 'pushm', 'feature': CF_USE1 | CF_USE2, 'cmt': "Push registers onto stack"},
|
|
{'name': 'popm', 'feature': CF_USE1 | CF_CHG2, 'cmt': "Pop registers from the stack"},
|
|
|
|
# MSP430X address instructions
|
|
{'name': 'mova', 'feature': CF_USE1 | CF_CHG2, 'cmt': "Move source to destination"},
|
|
{'name': 'cmpa', 'feature': CF_USE1 | CF_USE2, 'cmt': "Compare source and destination"},
|
|
{'name': 'adda', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Add source to destination"},
|
|
{'name': 'suba', 'feature': CF_USE1 | CF_USE2 | CF_CHG2, 'cmt': "Subtract source from destination"},
|
|
|
|
# one-operand instructions
|
|
{'name': 'rrc', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Rotate right through C"},
|
|
{'name': 'swpb', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Swap bytes"},
|
|
{'name': 'rra', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Rotate right arithmetically"},
|
|
{'name': 'sxt', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Extend sign (8 bits to 16)"},
|
|
{'name': 'push', 'feature': CF_USE1 , 'cmt': "Push onto stack"},
|
|
{'name': 'call', 'feature': CF_USE1 | CF_CALL, 'cmt': "Call subroutine"},
|
|
{'name': 'reti', 'feature': CF_STOP , 'cmt': "Return from interrupt"},
|
|
|
|
# MSP430X instructions
|
|
{'name': 'rrcx', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Rotate right through carry"},
|
|
{'name': 'swpbx','feature': CF_USE1 | CF_CHG1, 'cmt': "Exchange low byte with high byte"},
|
|
{'name': 'rrax', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Rotate right arithmetically"},
|
|
{'name': 'sxtx', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Extend sign of lower byte"},
|
|
{'name': 'pushx','feature': CF_USE1 , 'cmt': "Push onto stack"},
|
|
{'name': 'calla','feature': CF_USE1 , 'cmt': "Call subroutine (20-bit)"},
|
|
{'name': 'rrux', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Rotate right unsigned"},
|
|
|
|
# jumps
|
|
{'name': 'jnz', 'feature': CF_USE1 , 'cmt': "Jump if not zero/not equal"},
|
|
{'name': 'jz', 'feature': CF_USE1 , 'cmt': "Jump if zero/equal"},
|
|
{'name': 'jnc', 'feature': CF_USE1 , 'cmt': "Jump if no carry/lower (unsigned)"},
|
|
{'name': 'jc', 'feature': CF_USE1 , 'cmt': "Jump if carry/higher or same (unsigned)"},
|
|
{'name': 'jn', 'feature': CF_USE1 , 'cmt': "Jump if negative"},
|
|
{'name': 'jge', 'feature': CF_USE1 , 'cmt': "Jump if greater or equal (signed)"},
|
|
{'name': 'jl', 'feature': CF_USE1 , 'cmt': "Jump if less (signed)"},
|
|
{'name': 'jmp', 'feature': CF_USE1 | CF_STOP, 'cmt': "Jump unconditionally"},
|
|
|
|
# emulated instructions
|
|
{'name': 'adc', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Add carry to destination"},
|
|
{'name': 'br', 'feature': CF_USE1 | CF_STOP, 'cmt': "Branch to destination"},
|
|
{'name': 'clr', 'feature': CF_CHG1 , 'cmt': "Clear destination"},
|
|
{'name': 'clrc', 'feature': 0 , 'cmt': "Clear carry bit"},
|
|
{'name': 'clrn', 'feature': 0 , 'cmt': "Clear negative bit"},
|
|
{'name': 'clrz', 'feature': 0 , 'cmt': "Clear zero bit"},
|
|
{'name': 'dadc', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Add carry decimally to destination"},
|
|
{'name': 'dec', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Decrement destination"},
|
|
{'name': 'decd', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Double-decrement destination"},
|
|
{'name': 'dint', 'feature': 0 , 'cmt': "Disable general interrupts"},
|
|
{'name': 'eint', 'feature': 0 , 'cmt': "Enable general interrupts"},
|
|
{'name': 'inc', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Increment destination"},
|
|
{'name': 'incd', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Double-increment destination"},
|
|
{'name': 'inv', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Invert destination"},
|
|
{'name': 'nop', 'feature': 0 , 'cmt': "No operation"},
|
|
{'name': 'pop', 'feature': CF_CHG1 , 'cmt': "Pop from the stack"},
|
|
{'name': 'ret', 'feature': CF_STOP , 'cmt': "Return from subroutine"},
|
|
{'name': 'rla', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Rotate left arithmetically"},
|
|
{'name': 'rlc', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Rotate left through carry"},
|
|
{'name': 'sbc', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Substract borrow (=NOT carry) from destination"},
|
|
{'name': 'setc', 'feature': 0 , 'cmt': "Set carry bit"},
|
|
{'name': 'setn', 'feature': 0 , 'cmt': "Set negative bit"},
|
|
{'name': 'setz', 'feature': 0 , 'cmt': "Set zero bit"},
|
|
{'name': 'tst', 'feature': CF_USE1 , 'cmt': "Test destination"},
|
|
# MSP430X instructions
|
|
{'name': 'adcx', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Add carry to destination"},
|
|
{'name': 'bra', 'feature': CF_USE1 | CF_STOP, 'cmt': "Branch indirect to destination"},
|
|
{'name': 'reta', 'feature': CF_STOP , 'cmt': "Return from subroutine"},
|
|
{'name': 'popa', 'feature': CF_CHG1 , 'cmt': "Pop from the stack"},
|
|
{'name': 'clra', 'feature': CF_CHG1 , 'cmt': "Clear destination"},
|
|
{'name': 'clrx', 'feature': CF_CHG1 , 'cmt': "Clear destination"},
|
|
{'name': 'dadcx','feature': CF_USE1 | CF_CHG1, 'cmt': "Add carry decimally to destination"},
|
|
{'name': 'decx', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Decrement destination"},
|
|
{'name': 'decda','feature': CF_USE1 | CF_CHG1, 'cmt': "Double-decrement destination"},
|
|
{'name': 'decdx','feature': CF_USE1 | CF_CHG1, 'cmt': "Double-decrement destination"},
|
|
{'name': 'incx', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Increment destination"},
|
|
{'name': 'incda','feature': CF_USE1 | CF_CHG1, 'cmt': "Double-increment destination"},
|
|
{'name': 'incdx','feature': CF_USE1 | CF_CHG1, 'cmt': "Double-increment destination"},
|
|
{'name': 'invx', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Invert destination"},
|
|
{'name': 'rlax', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Rotate left arithmetically"},
|
|
{'name': 'rlcx', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Rotate left through carry"},
|
|
{'name': 'sbcx', 'feature': CF_USE1 | CF_CHG1, 'cmt': "Substract borrow (=NOT carry) from destination"},
|
|
{'name': 'tsta', 'feature': CF_USE1 , 'cmt': "Test destination"},
|
|
{'name': 'tstx', 'feature': CF_USE1 , 'cmt': "Test destination"},
|
|
{'name': 'popx', 'feature': CF_CHG1 , 'cmt': "Pop from the stack"},
|
|
]
|
|
|
|
# icode of the first instruction
|
|
instruc_start = 0
|
|
|
|
# icode of the last instruction + 1
|
|
instruc_end = len(instruc) + 1
|
|
|
|
# Size of long double (tbyte) for this processor (meaningful only if ash.a_tbyte != NULL) (optional)
|
|
# tbyte_size = 0
|
|
|
|
#
|
|
# Number of digits in floating numbers after the decimal point.
|
|
# If an element of this array equals 0, then the corresponding
|
|
# floating point data is not used for the processor.
|
|
# This array is used to align numbers in the output.
|
|
# real_width[0] - number of digits for short floats (only PDP-11 has them)
|
|
# real_width[1] - number of digits for "float"
|
|
# real_width[2] - number of digits for "double"
|
|
# real_width[3] - number of digits for "long double"
|
|
# Example: IBM PC module has { 0,7,15,19 }
|
|
#
|
|
# (optional)
|
|
real_width = (0, 7, 15, 19)
|
|
|
|
|
|
# only one assembler is supported
|
|
assembler = {
|
|
# flag
|
|
'flag' : ASH_HEXF0 | ASD_DECF0 | ASO_OCTF5 | ASB_BINF0 | AS_N2CHR,
|
|
|
|
# user defined flags (local only for IDP) (optional)
|
|
'uflag' : 0,
|
|
|
|
# Assembler name (displayed in menus)
|
|
'name': "Generic MSP430 assembler",
|
|
|
|
# array of automatically generated header lines they appear at the start of disassembled text (optional)
|
|
'header': [".msp430"],
|
|
|
|
# org directive
|
|
'origin': ".org",
|
|
|
|
# end directive
|
|
'end': ".end",
|
|
|
|
# comment string (see also cmnt2)
|
|
'cmnt': ";",
|
|
|
|
# ASCII string delimiter
|
|
'ascsep': "\"",
|
|
|
|
# ASCII char constant delimiter
|
|
'accsep': "'",
|
|
|
|
# ASCII special chars (they can't appear in character and ascii constants)
|
|
'esccodes': "\"'",
|
|
|
|
#
|
|
# Data representation (db,dw,...):
|
|
#
|
|
# ASCII string directive
|
|
'a_ascii': ".char",
|
|
|
|
# byte directive
|
|
'a_byte': ".byte",
|
|
|
|
# word directive
|
|
'a_word': ".short",
|
|
|
|
# dword (32 bits)
|
|
'a_dword': ".long",
|
|
|
|
# qword (64 bits)
|
|
'a_qword': ".quad",
|
|
|
|
# float; 4bytes; remove if not allowed
|
|
'a_float': ".float",
|
|
|
|
# double ; 8bytes; remove if not allowed
|
|
'a_double': ".double",
|
|
|
|
# uninitialized data directive (should include '%s' for the size of data)
|
|
'a_bss': ".space %s",
|
|
|
|
# 'equ' Used if AS_UNEQU is set (optional)
|
|
'a_equ': ".equ",
|
|
|
|
# 'seg ' prefix (example: push seg seg001)
|
|
'a_seg': "seg",
|
|
|
|
# current IP (instruction pointer) symbol in assembler
|
|
'a_curip': "$",
|
|
|
|
# "public" name keyword. NULL-gen default, ""-do not generate
|
|
'a_public': ".def",
|
|
|
|
# "weak" name keyword. NULL-gen default, ""-do not generate
|
|
'a_weak': "",
|
|
|
|
# "extrn" name keyword
|
|
'a_extrn': ".ref",
|
|
|
|
# "comm" (communal variable)
|
|
'a_comdef': "",
|
|
|
|
# "align" keyword
|
|
'a_align': ".align",
|
|
|
|
# Left and right braces used in complex expressions
|
|
'lbrace': "(",
|
|
'rbrace': ")",
|
|
|
|
# % mod assembler time operation
|
|
'a_mod': "%",
|
|
|
|
# & bit and assembler time operation
|
|
'a_band': "&",
|
|
|
|
# | bit or assembler time operation
|
|
'a_bor': "|",
|
|
|
|
# ^ bit xor assembler time operation
|
|
'a_xor': "^",
|
|
|
|
# ~ bit not assembler time operation
|
|
'a_bnot': "~",
|
|
|
|
# << shift left assembler time operation
|
|
'a_shl': "<<",
|
|
|
|
# >> shift right assembler time operation
|
|
'a_shr': ">>",
|
|
|
|
# size of type (format string) (optional)
|
|
'a_sizeof_fmt': "size %s",
|
|
|
|
'flag2': 0,
|
|
|
|
# the include directive (format string) (optional)
|
|
'a_include_fmt': '.include "%s"',
|
|
} # Assembler
|
|
|
|
def ev_get_frame_retsize(self, frsize, pfn):
|
|
ida_pro.int_pointer.frompointer(frsize).assign(2)
|
|
return 1
|
|
|
|
def ev_get_autocmt(self, insn):
|
|
if 'cmt' in self.instruc[insn.itype]:
|
|
return self.instruc[insn.itype]['cmt']
|
|
|
|
# ----------------------------------------------------------------------
|
|
def ev_is_sane_insn(self, insn, no_crefs):
|
|
w = get_wide_word(insn.ea)
|
|
if w == 0 or w == 0xFFFF:
|
|
return -1
|
|
return 1
|
|
|
|
# ----------------------------------------------------------------------
|
|
def is_movpc(self, insn):
|
|
# mov xxx, PC
|
|
return insn.itype == self.itype_mov and insn.Op2.is_reg(self.ireg_PC) and insn.auxpref == AUX_WORD
|
|
|
|
# ----------------------------------------------------------------------
|
|
def changes_pc(self, insn):
|
|
Feature = insn.get_canon_feature()
|
|
if (Feature & CF_CHG2) and insn.Op2.is_reg(self.ireg_PC):
|
|
return True
|
|
if (Feature & CF_CHG1) and insn.Op1.is_reg(self.ireg_PC):
|
|
return True
|
|
return False
|
|
|
|
# ----------------------------------------------------------------------
|
|
def handle_operand(self, insn, op, isRead):
|
|
flags = get_flags(insn.ea)
|
|
is_offs = is_off(flags, op.n)
|
|
dref_flag = dr_R if isRead else dr_W
|
|
def_arg = is_defarg(flags, op.n)
|
|
optype = op.type
|
|
|
|
itype = insn.itype
|
|
# create code xrefs
|
|
if optype == o_imm:
|
|
makeoff = False
|
|
if itype in [self.itype_call, self.itype_calla]:
|
|
# call #func
|
|
insn.add_cref(op.value, op.offb, fl_CN)
|
|
makeoff = True
|
|
elif self.is_movpc(insn) or insn.itype in [self.itype_br, self.itype_bra]:
|
|
# mov #addr, PC
|
|
insn.add_cref(op.value, op.offb, fl_JN)
|
|
makeoff = True
|
|
if makeoff and not def_arg:
|
|
op_plain_offset(insn.ea, op.n, insn.cs)
|
|
is_offs = True
|
|
if is_offs:
|
|
insn.add_off_drefs(op, dr_O, 0)
|
|
# create data xrefs
|
|
elif optype == o_displ:
|
|
# delta(reg)
|
|
if is_offs:
|
|
insn.add_off_drefs(op, dref_flag, OOF_ADDR)
|
|
elif may_create_stkvars() and not def_arg and op.reg == self.ireg_SP:
|
|
# var_x(SP)
|
|
pfn = get_func(insn.ea)
|
|
if pfn and insn.create_stkvar(op, op.addr, STKVAR_VALID_SIZE):
|
|
op_stkvar(insn.ea, op.n)
|
|
elif optype == o_mem:
|
|
insn.create_op_data(op.addr, op)
|
|
insn.add_dref(op.addr, op.offb, dref_flag)
|
|
elif optype == o_near:
|
|
insn.add_cref(op.addr, op.offb, fl_JN)
|
|
|
|
# ----------------------------------------------------------------------
|
|
def add_stkpnt(self, pfn, insn, v):
|
|
if pfn:
|
|
end = insn.ea + insn.size
|
|
if not is_fixed_spd(end):
|
|
ida_frame.add_auto_stkpnt(pfn, end, v)
|
|
|
|
# ----------------------------------------------------------------------
|
|
def trace_sp(self, insn):
|
|
"""
|
|
Trace the value of the SP and create an SP change point if the current
|
|
instruction modifies the SP.
|
|
"""
|
|
pfn = get_func(insn.ea)
|
|
if not pfn:
|
|
return
|
|
spofs = 0
|
|
if insn.itype in [self.itype_add, self.itype_addx, self.itype_adda, self.itype_addc, self.itype_addcx,
|
|
self.itype_sub, self.itype_subx, self.itype_suba, self.itype_subc, self.itype_subcx] and \
|
|
insn.Op2.is_reg(self.ireg_SP) and insn.auxpref in [AUX_WORD, AUX_A, AUX_AX] and \
|
|
insn.Op1.type == o_imm:
|
|
# add.w #xxx, SP
|
|
# subc.w #xxx, SP
|
|
if insn.auxpref == AUX_WORD:
|
|
spofs = SIGNEXT(insn.Op1.value, 16)
|
|
else:
|
|
spofs = SIGNEXT(insn.Op1.value, 20)
|
|
if insn.itype in [self.itype_sub, self.itype_suba, self.itype_subc, self.itype_subx, self.itype_subcx]:
|
|
spofs = -spofs
|
|
elif insn.itype in [self.itype_incd, self.itype_decd, self.itype_incdx,
|
|
self.itype_decdx, self.itype_incda, self.itype_decda] and \
|
|
insn.Op1.is_reg(self.ireg_SP) and insn.auxpref in [AUX_WORD, AUX_A, AUX_AX]:
|
|
spofs = 2 if insn.itype in [self.itype_incd, self.itype_incdx, self.itype_incda] else -2
|
|
self.add_stkpnt(pfn, insn, spofs)
|
|
elif insn.itype == self.itype_push:
|
|
spofs = -2
|
|
elif insn.itype in [self.itype_popm, self.itype_pushm, self.itype_popx, self.itype_pushx]:
|
|
# popm.a #n, reg -> +n*4
|
|
# popm.w #n, reg -> +n*2
|
|
# popx.a reg -> +4
|
|
# popx.w reg -> +2
|
|
if insn.itype in [self.itype_popm, self.itype_pushm]:
|
|
count = insn.Op1.value
|
|
else:
|
|
count = 1
|
|
spofs = 1 if insn.itype == self.itype_popm else -1
|
|
if insn.auxpref == AUX_A:
|
|
spofs *= count * 4
|
|
else:
|
|
spofs *= count * 2
|
|
elif insn.itype == self.itype_pop or is_autoinc(insn.Op1, self.ireg_SP):
|
|
# pop R7 or mov.w @SP+, R7
|
|
if insn.auxpref in [AUX_A, AUX_AX]:
|
|
spofs = 4
|
|
else:
|
|
spofs = 2
|
|
|
|
if spofs != 0:
|
|
self.add_stkpnt(pfn, insn, spofs)
|
|
|
|
# ----------------------------------------------------------------------
|
|
def check_switch(self, insn):
|
|
# detect switches and set switch info
|
|
#
|
|
# cmp.w #nn, Rx
|
|
# jc default
|
|
# [mov.w Rx, Ry]
|
|
# rla.w Ry, Ry
|
|
# br jtbl(Ry)
|
|
# jtbl .short case0, .short case1
|
|
if get_switch_info(insn.ea):
|
|
return
|
|
ok = False
|
|
si = switch_info_t()
|
|
|
|
# mov.w jtbl(Ry), PC
|
|
if (self.is_movpc(insn) or insn.itype in [self.itype_br, self.itype_bra]) and insn.Op1.type == o_displ:
|
|
si.jumps = insn.Op1.addr # jump table address
|
|
Ry = insn.Op1.reg
|
|
ok = True
|
|
# add.w Ry, Ry | rla.w Ry
|
|
prev = insn_t()
|
|
if decode_prev_insn(prev, insn.ea) != BADADDR and prev.auxpref == AUX_WORD:
|
|
ok = prev.itype == self.itype_add and prev.Op1.is_reg(Ry) and prev.Op2.is_reg(Ry) or \
|
|
prev.itype == self.itype_rla and prev.Op1.is_reg(Ry)
|
|
else:
|
|
ok = False
|
|
if ok and decode_prev_insn(prev, prev.ea) != BADADDR:
|
|
# mov.w Rx, Ry
|
|
if prev.itype == self.itype_mov and \
|
|
prev.Op2.is_reg(Ry) and \
|
|
prev.Op1.type == o_reg and \
|
|
prev.auxpref == AUX_WORD:
|
|
Rx = prev.Op1.reg
|
|
ok = decode_prev_insn(prev, prev.ea) != BADADDR
|
|
else:
|
|
Rx = Ry
|
|
else:
|
|
ok = False
|
|
|
|
# jc default
|
|
if ok and prev.itype == self.itype_jc:
|
|
si.defjump = prev.Op1.addr
|
|
else:
|
|
ok = False
|
|
|
|
# cmp.w #nn, Rx
|
|
if ok and decode_prev_insn(prev, prev.ea) == BADADDR or \
|
|
prev.itype != self.itype_cmp or \
|
|
prev.Op1.type != o_imm or \
|
|
not prev.Op2.is_reg(Rx) or \
|
|
prev.auxpref != AUX_WORD:
|
|
ok = False
|
|
else:
|
|
si.ncases = prev.Op1.value
|
|
si.lowcase = 0
|
|
si.startea = prev.ea
|
|
si.set_expr(Rx, dt_word)
|
|
|
|
if ok:
|
|
# make offset to the jump table
|
|
op_plain_offset(insn.ea, 0, insn.cs)
|
|
set_switch_info(insn.ea, si)
|
|
create_switch_table(insn.ea, si)
|
|
|
|
# ----------------------------------------------------------------------
|
|
# The following callbacks are mandatory
|
|
#
|
|
def ev_emu_insn(self, insn):
|
|
aux = self.get_auxpref(insn)
|
|
Feature = insn.get_canon_feature()
|
|
|
|
if Feature & CF_USE1:
|
|
self.handle_operand(insn, insn.Op1, 1)
|
|
if Feature & CF_CHG1:
|
|
self.handle_operand(insn, insn.Op1, 0)
|
|
if Feature & CF_USE2:
|
|
self.handle_operand(insn, insn.Op2, 1)
|
|
if Feature & CF_CHG2:
|
|
self.handle_operand(insn, insn.Op2, 0)
|
|
if Feature & CF_JUMP:
|
|
remember_problem(PR_JUMP, insn.ea)
|
|
|
|
# is it an unconditional jump?
|
|
uncond_jmp = insn.itype in [self.itype_jmp, self.itype_br, self.itype_bra] or self.changes_pc(insn)
|
|
|
|
# add flow
|
|
flow = (Feature & CF_STOP == 0) and not uncond_jmp
|
|
if flow:
|
|
add_cref(insn.ea, insn.ea + insn.size, fl_F)
|
|
else:
|
|
self.check_switch(insn)
|
|
|
|
# trace the stack pointer if:
|
|
# - it is the second analysis pass
|
|
# - the stack pointer tracing is allowed
|
|
if may_trace_sp():
|
|
if flow:
|
|
self.trace_sp(insn) # trace modification of SP register
|
|
else:
|
|
idc.recalc_spd(insn.ea) # recalculate SP register for the next insn
|
|
|
|
return True
|
|
|
|
# ----------------------------------------------------------------------
|
|
def ev_out_operand(self, ctx, op):
|
|
optype = op.type
|
|
fl = op.specval
|
|
signed = 0
|
|
sz = ctx.insn.auxpref & AUX_SIZEMASK
|
|
|
|
if optype == o_reg:
|
|
ctx.out_register(self.reg_names[op.reg])
|
|
elif optype == o_imm:
|
|
ctx.out_symbol('#')
|
|
op2 = copy.copy(op)
|
|
if sz == AUX_BYTE:
|
|
op2.value &= 0xFF
|
|
elif sz == AUX_WORD:
|
|
op2.value &= 0xFFFF
|
|
else:
|
|
op2.value &= 0xFFFFF
|
|
ctx.out_value(op2, OOFW_IMM | signed )
|
|
elif optype in [o_near, o_mem]:
|
|
if optype == o_mem and fl == FL_ABSOLUTE:
|
|
ctx.out_symbol('&')
|
|
r = ctx.out_name_expr(op, op.addr, BADADDR)
|
|
if not r:
|
|
ctx.out_tagon(COLOR_ERROR)
|
|
ctx.out_btoa(op.addr, 16)
|
|
ctx.out_tagoff(COLOR_ERROR)
|
|
remember_problem(PR_NONAME, ctx.insn.ea)
|
|
elif optype == o_displ:
|
|
# 16-bit index is signed
|
|
width = OOFW_16
|
|
sign = OOF_SIGNED
|
|
if sz in [AUX_A, AUX_AX] or op.dtype == dt_dword:
|
|
# 20-bit index is not signed
|
|
width = OOFW_24
|
|
sign = 0
|
|
ctx.out_value(op, OOF_ADDR | signed | width )
|
|
ctx.out_symbol('(')
|
|
ctx.out_register(self.reg_names[op.reg])
|
|
ctx.out_symbol(')')
|
|
elif optype == o_phrase:
|
|
ctx.out_symbol('@')
|
|
ctx.out_register(self.reg_names[op.reg])
|
|
if fl == FL_AUTOINC:
|
|
ctx.out_symbol('+')
|
|
else:
|
|
return False
|
|
|
|
return True
|
|
|
|
# ----------------------------------------------------------------------
|
|
def ev_out_mnem(self, ctx):
|
|
|
|
postfix = ""
|
|
|
|
# add postfix if necessary
|
|
sz = ctx.insn.auxpref & AUX_SIZEMASK
|
|
if sz == AUX_BYTE:
|
|
postfix = ".b"
|
|
elif sz == AUX_WORD:
|
|
postfix = ".w"
|
|
elif sz == AUX_A:
|
|
postfix = ".a"
|
|
|
|
# first argument (8) is the width of the mnemonic field
|
|
ctx.out_mnem(8, postfix)
|
|
return 1
|
|
|
|
# ----------------------------------------------------------------------
|
|
def ev_out_insn(self, ctx):
|
|
"""
|
|
Generate text representation of an instruction in 'ctx.insn' structure.
|
|
This function shouldn't change the database, flags or anything else.
|
|
All these actions should be performed only by emu() function.
|
|
Returns: nothing
|
|
"""
|
|
# do we need to print a modifier line first?
|
|
if ctx.insn.auxpref & (AUX_REPREG | AUX_REPIMM | AUX_ZC):
|
|
segpref = ctx.insn.segpref
|
|
if sys.version_info.major < 3:
|
|
segpref = ord(segpref)
|
|
if ctx.insn.auxpref & AUX_ZC:
|
|
ctx.out_line(".zc", COLOR_INSN)
|
|
ctx.flush_outbuf()
|
|
if ctx.insn.auxpref & AUX_REPREG:
|
|
ctx.out_line(".rpt", COLOR_INSN)
|
|
ctx.out_char(' ')
|
|
ctx.out_register(self.reg_names[segpref])
|
|
ctx.flush_outbuf()
|
|
if ctx.insn.auxpref & AUX_REPIMM:
|
|
ctx.out_line(".rpt", COLOR_INSN)
|
|
ctx.out_char(' ')
|
|
ctx.out_symbol('#')
|
|
ctx.out_long(segpref, 10)
|
|
ctx.flush_outbuf()
|
|
#ident next line
|
|
ctx.out_char(' ')
|
|
|
|
ctx.out_mnemonic()
|
|
|
|
# output first operand
|
|
# kernel will call out_operand()
|
|
if ctx.insn.Op1.type != o_void:
|
|
ctx.out_one_operand(0)
|
|
|
|
# output the rest of operands separated by commas
|
|
for i in range(1, 3):
|
|
if ctx.insn[i].type == o_void:
|
|
break
|
|
ctx.out_symbol(',')
|
|
ctx.out_char(' ')
|
|
ctx.out_one_operand(i)
|
|
|
|
ctx.set_gen_cmt() # generate comment at the next call to MakeLine()
|
|
ctx.flush_outbuf()
|
|
return True
|
|
|
|
# ----------------------------------------------------------------------
|
|
# fill operand fields from decoded instruction parts
|
|
# op: operand to be filled in
|
|
# reg: register number
|
|
# A: adressing mode
|
|
# BW: value of the B/W (byte/word) field
|
|
# can be DLEN_AWORD for 20-bit instructions
|
|
# is_source: True if filling source operand
|
|
# is_cg: can use constant generator
|
|
# extw: 20-bit extension word
|
|
def fill_op(self, insn, op, reg, A, BW, is_source, is_cg = False, extw = None):
|
|
op.reg = reg
|
|
topaddr = 0 # top 4 bits of an address value
|
|
if extw:
|
|
AL = BIT(extw, 6)
|
|
if AL == 0 and BW == 1:
|
|
BW = DLEN_AWORD
|
|
if is_source:
|
|
topaddr = BITS(extw, 10, 7)
|
|
else:
|
|
topaddr = BITS(extw, 3, 0)
|
|
if BW == DLEN_WORD:
|
|
op.dtype = dt_word
|
|
elif BW == DLEN_BYTE:
|
|
op.dtype = dt_byte
|
|
else:
|
|
# 20-bit
|
|
op.dtype = dt_dword
|
|
if is_cg:
|
|
# check for constant generators
|
|
if reg == self.ireg_SR and A >= 2 and A <= 3:
|
|
op.type = o_imm
|
|
op.value = [4, 8] [A-2]
|
|
return
|
|
elif reg == self.ireg_R3:
|
|
op.type = o_imm
|
|
op.value = [0, 1, 2, -1] [A]
|
|
return
|
|
if A == AM_REGISTER:
|
|
# register mode
|
|
op.type = o_reg
|
|
op.dtype = dt_word
|
|
elif A == AM_INDEXED:
|
|
# indexed mode
|
|
if reg == self.ireg_SR:
|
|
# absolute address mode
|
|
op.type = o_mem
|
|
op.specval = FL_ABSOLUTE
|
|
op.offb = insn.size
|
|
op.addr = insn.get_next_word() | (topaddr << 16)
|
|
else:
|
|
# map it to IDA's displacement
|
|
op.type = o_displ
|
|
op.offb = insn.size
|
|
pcval = insn.ip + insn.size
|
|
op.addr = insn.get_next_word() | (topaddr << 16)
|
|
if reg == self.ireg_PC:
|
|
# symbolic addressing mode: address = PC + simm16
|
|
# 1) if PC is below 64K, the result is wrapped to be below 64K
|
|
# 2) if PC is above 64K, the result is used as-is (can be below or above 64K)
|
|
# 3) for MSP430X instructions delta is simm20, result not wrapped
|
|
# apparently the PC value is the address of the index word, not next instruction!
|
|
if extw:
|
|
op.addr = SIGNEXT(op.addr, 20) + pcval
|
|
else:
|
|
op.addr = SIGNEXT(op.addr, 16) + pcval
|
|
if pcval < 0x10000:
|
|
op.addr &= 0xFFFF
|
|
op.type = o_mem
|
|
op.specval = FL_SYMBOLIC
|
|
# slau208p.pdf: All addresses, indexes, and immediate numbers have
|
|
# 20-bit values when preceded by the extension word.
|
|
if extw:
|
|
op.dtype = dt_dword
|
|
elif A == AM_INDIRECT:
|
|
# Indirect register mode
|
|
# map it to o_phrase
|
|
op.type = o_phrase
|
|
op.specval = FL_INDIRECT
|
|
elif A == AM_AUTOINC:
|
|
# Indirect autoincrement
|
|
# map it to o_phrase
|
|
if reg == self.ireg_PC:
|
|
#this is actually immediate mode
|
|
op.dtype = dt_dword if extw else dt_word
|
|
op.type = o_imm
|
|
op.offb = insn.size
|
|
op.value = insn.get_next_word() | (topaddr << 16)
|
|
else:
|
|
op.type = o_phrase
|
|
op.specval = FL_AUTOINC
|
|
elif A in [AM_IMM20, AM_ABS20, AM_SYM20]:
|
|
# reg is the high 4 bits, low 16 bits follow
|
|
val = (reg << 16) | insn.get_next_word()
|
|
if A == AM_IMM20:
|
|
op.dtype = dt_dword
|
|
op.value = val
|
|
op.type = o_imm
|
|
else:
|
|
# &abs20
|
|
op.addr = val
|
|
op.type = o_mem
|
|
if A == AM_SYM20:
|
|
# symbolic addressing mode: address = PC + imm20
|
|
# no sign-extension or wrapping is done
|
|
pcval = insn.ip + insn.size
|
|
op.addr += pcval
|
|
op.specval = FL_SYMBOLIC
|
|
else:
|
|
op.specval = FL_ABSOLUTE
|
|
else:
|
|
warning("bad A(%d) in fill_op" % A)
|
|
|
|
# ----------------------------------------------------------------------
|
|
def handle_reg_extw(self, insn, extw):
|
|
# Register Mode Extension Word
|
|
# 15 ... 12 11 10 9 8 7 6 5 4 3 0
|
|
# +---------+--------+----+---+-----+---+---+------+
|
|
# | 0001 | 1 | 00 | ZC | # | A/L | 0 | 0 | R/n-1|
|
|
# +---------+--------+----+---+-----+---+---+------+
|
|
ZC = BIT(extw, 8)
|
|
repreg = BIT(extw, 7)
|
|
rep = BITS(extw, 3, 0)
|
|
if rep:
|
|
if repreg:
|
|
insn.auxpref |= AUX_REPREG
|
|
insn.segpref = rep
|
|
else:
|
|
insn.auxpref |= AUX_REPIMM
|
|
insn.segpref = rep + 1
|
|
if ZC:
|
|
if insn.itype == self.itype_rrcx:
|
|
insn.itype = self.itype_rrux
|
|
else:
|
|
insn.auxpref |= AUX_ZC
|
|
|
|
# ----------------------------------------------------------------------
|
|
def decode_format_I(self, insn, w, extw):
|
|
# Double-Operand (Format I) Instructions
|
|
#
|
|
# 15 ... 12 11 ... 8 7 6 5 4 3 0
|
|
# +---------+--------+----+-----+----+------+
|
|
# | Op-code | Rsrc | Ad | B/W | As | Rdst |
|
|
# +---------+--------+----+-----+----+------+
|
|
# | Source or destination 15:0 |
|
|
# +-----------------------------------------+
|
|
# | Destination 15:0 |
|
|
# +-----------------------------------------+
|
|
opc = BITS(w, 15, 12)
|
|
As = BITS(w, 5, 4)
|
|
Ad = BIT(w, 7)
|
|
Rsrc = BITS(w, 11, 8)
|
|
Rdst = BITS(w, 3, 0)
|
|
BW = BIT(w, 6)
|
|
if opc < 4:
|
|
# something went wrong
|
|
insn.size = 0
|
|
else:
|
|
if extw:
|
|
AL = BIT(extw, 6)
|
|
if AL == 0 and BW == 1:
|
|
BW = DLEN_AWORD
|
|
insn.itype = self.itype_movx + (opc-4)
|
|
else:
|
|
insn.itype = self.itype_mov + (opc-4)
|
|
self.fill_op(insn, insn.Op1, Rsrc, As, BW, True, True, extw)
|
|
self.fill_op(insn, insn.Op2, Rdst, Ad, BW, False, False, extw)
|
|
insn.auxpref = BW + AUX_WORD
|
|
if extw and As == AM_REGISTER and Ad == AM_REGISTER:
|
|
self.handle_reg_extw(insn, extw)
|
|
|
|
# ----------------------------------------------------------------------
|
|
def decode_format_II(self, insn, w, extw):
|
|
# Single-Operand (Format II) Instructions
|
|
#
|
|
# 15 10 9 7 6 5 4 3 0
|
|
# +-------------+---------+-----+----+------+
|
|
# | 0 0 0 1 0 0 | Op-code | B/W | Ad | Rdst |
|
|
# +-------------+---------+-----+----+------+
|
|
# | Destination 15:0 |
|
|
# +-----------------------------------------+
|
|
opc = BITS(w, 9, 7)
|
|
Ad = BITS(w, 5, 4)
|
|
Rdst = BITS(w, 3, 0)
|
|
BW = BIT(w, 6)
|
|
if opc in [6, 7]:
|
|
if extw:
|
|
return 0
|
|
return self.decode_430x_calla(insn, w)
|
|
if extw:
|
|
AL = BIT(extw, 6)
|
|
if AL == 0 and BW == 1:
|
|
BW = DLEN_AWORD
|
|
insn.itype = self.itype_rrcx + opc
|
|
else:
|
|
insn.itype = self.itype_rrc + opc
|
|
self.fill_op(insn, insn.Op1, Rdst, Ad, BW, False, True, extw)
|
|
insn.auxpref = BW + AUX_WORD
|
|
if insn.itype in [self.itype_swpb, self.itype_sxt, self.itype_call, self.itype_reti]:
|
|
# these commands have no suffix and should have BW set to 0
|
|
if BW == 0:
|
|
insn.auxpref = AUX_NOSUF
|
|
if insn.itype == self.itype_reti:
|
|
# Ad and Rdst should be 0
|
|
if Ad == 0 and Rdst == 0:
|
|
insn.Op1.type = o_void
|
|
else:
|
|
# bad instruction
|
|
insn.itype = self.itype_null
|
|
else:
|
|
# bad instruction
|
|
insn.itype = self.itype_null
|
|
if extw and Ad == AM_REGISTER:
|
|
self.handle_reg_extw(insn, extw)
|
|
|
|
# ----------------------------------------------------------------------
|
|
def decode_jump(self, insn, w):
|
|
# Jump Instructions
|
|
#
|
|
# 15 13 12 10 9 0
|
|
# +---------+-------+----------------------+
|
|
# | 0 0 1 | C | 10-bit PC offset |
|
|
# +---------+-------+----------------------+
|
|
C = BITS(w, 12, 10)
|
|
offs = BITS(w, 9, 0)
|
|
offs = SIGNEXT(offs, 10)
|
|
insn.Op1.type = o_near
|
|
insn.Op1.addr = insn.ea + 2 + offs*2
|
|
insn.itype = self.itype_jnz + C
|
|
|
|
# ----------------------------------------------------------------------
|
|
def decode_430x_mova(self, insn, w):
|
|
# MSP430X Address Instructions
|
|
# 15 12 11 8 7 4 3 0
|
|
# 0 0 0 0 src 0 0 0 0 dst MOVA @Rsrc,Rdst
|
|
# 0 0 0 0 src 0 0 0 1 dst MOVA @Rsrc+,Rdst
|
|
# 0 0 0 0 abs 0 0 1 0 dst MOVA &abs20,Rdst
|
|
# 0 0 0 0 src 0 0 1 1 dst MOVA x(Rsrc),Rdst
|
|
|
|
# 0 0 0 0 n-1 00 0 1 0 0 dst RRCM.A #n,Rdst
|
|
# 0 0 0 0 n-1 01 0 1 0 0 dst RRAM.A #n,Rdst
|
|
# 0 0 0 0 n-1 10 0 1 0 0 dst RLAM.A #n,Rdst
|
|
# 0 0 0 0 n-1 11 0 1 0 0 dst RRUM.A #n,Rdst
|
|
|
|
# 0 0 0 0 n-1 00 0 1 0 1 dst RRCM.W #n,Rdst
|
|
# 0 0 0 0 n-1 01 0 1 0 1 dst RRAM.W #n,Rdst
|
|
# 0 0 0 0 n-1 10 0 1 0 1 dst RLAM.W #n,Rdst
|
|
# 0 0 0 0 n-1 11 0 1 0 1 dst RRUM.W #n,Rdst
|
|
|
|
# 0 0 0 0 src 0 1 1 0 abs MOVA Rsrc,&abs20
|
|
# 0 0 0 0 src 0 1 1 1 dst MOVA Rsrc,X(Rdst)
|
|
|
|
# 0 0 0 0 imm 1 0 0 0 dst MOVA #imm20,Rdst
|
|
# 0 0 0 0 imm 1 0 0 1 dst CMPA #imm20,Rdst
|
|
# 0 0 0 0 imm 1 0 1 0 dst ADDA #imm20,Rdst
|
|
# 0 0 0 0 imm 1 0 1 1 dst SUBA #imm20,Rdst
|
|
# 0 0 0 0 src 1 1 0 0 dst MOVA Rsrc,Rdst
|
|
# 0 0 0 0 src 1 1 0 1 dst CMPA Rsrc,Rdst
|
|
# 0 0 0 0 src 1 1 1 0 dst ADDA Rsrc,Rdst
|
|
# 0 0 0 0 src 1 1 1 1 dst SUBA Rsrc,Rdst
|
|
|
|
opc = BITS(w, 7, 4)
|
|
Rsrc = BITS(w, 11, 8)
|
|
Rdst = BITS(w, 3, 0)
|
|
tbl = [
|
|
# itype, operands addressing modes
|
|
# indexed by opcode[7:4]
|
|
[self.itype_mova, AM_INDIRECT, AM_REGISTER], # 0000 MOVA @Rsrc,Rdst
|
|
[self.itype_mova, AM_AUTOINC, AM_REGISTER], # 0001 MOVA @Rsrc+,Rdst
|
|
[self.itype_mova, AM_ABS20, AM_REGISTER], # 0010 MOVA &abs20,Rdst
|
|
[self.itype_mova, AM_INDEXED, AM_REGISTER], # 0011 MOVA X(Rsrc),Rdst
|
|
[-1, -1, -1 ], # 0100 Rxxx.A #n, Rdst
|
|
[-1, -1, -1 ], # 0101 Rxxx.W #n, Rdst
|
|
[self.itype_mova, AM_REGISTER, AM_ABS20 ], # 0110 MOVA Rsrc, &abs20
|
|
[self.itype_mova, AM_REGISTER, AM_INDEXED ], # 0111 MOVA Rsrc, X(Rdst)
|
|
[self.itype_mova, AM_IMM20, AM_REGISTER], # 1000 MOVA #imm20, Rdst
|
|
[self.itype_cmpa, AM_IMM20, AM_REGISTER], # 1001 CMPA #imm20, Rdst
|
|
[self.itype_adda, AM_IMM20, AM_REGISTER], # 1010 ADDA #imm20, Rdst
|
|
[self.itype_suba, AM_IMM20, AM_REGISTER], # 1011 SUBA #imm20, Rdst
|
|
[self.itype_mova, AM_REGISTER, AM_REGISTER], # 1100 MOVA Rsrc, Rdst
|
|
[self.itype_cmpa, AM_REGISTER, AM_REGISTER], # 1101 CMPA Rsrc, Rdst
|
|
[self.itype_adda, AM_REGISTER, AM_REGISTER], # 1110 ADDA Rsrc, Rdst
|
|
[self.itype_suba, AM_REGISTER, AM_REGISTER], # 1111 SUBA Rsrc, Rdst
|
|
]
|
|
row = tbl[opc]
|
|
if row[0] != -1:
|
|
insn.itype = row[0]
|
|
self.fill_op(insn, insn.Op1, Rsrc, row[1], 2, True, Rdst != 0)
|
|
self.fill_op(insn, insn.Op2, Rdst, row[2], 2, False, False)
|
|
insn.auxpref = AUX_AX
|
|
else:
|
|
# src[3:2] == n-1
|
|
# src[1:0] == insn id
|
|
insn.itype = self.itype_rrcm + (Rsrc & 0b11)
|
|
insn.Op1.type = o_imm
|
|
insn.Op1.dtype = dt_byte
|
|
insn.Op1.value = (Rsrc>>2) + 1
|
|
# opc[0]: 0=.A, 1=.W
|
|
BW = 0 if (opc & 1) else 2
|
|
self.fill_op(insn, insn.Op2, Rdst, AM_REGISTER, BW, False, False)
|
|
if opc & 1:
|
|
insn.auxpref = AUX_A
|
|
else:
|
|
insn.auxpref = AUX_WORD
|
|
|
|
# ----------------------------------------------------------------------
|
|
def decode_430x_calla(self, insn, w):
|
|
# MSP430X CALLA Instruction
|
|
opc = BITS(w, 7, 4)
|
|
Rdst = BITS(w, 3, 0)
|
|
tbl = [
|
|
# itype, dest addressing mode
|
|
# indexed by opcode[7:4]
|
|
[self.itype_reti, -1 ], # 0000 RETI
|
|
[self.itype_null, -1 ], # 0001 ----
|
|
[self.itype_null, -1 ], # 0010 ----
|
|
[self.itype_null, -1 ], # 0011 ----
|
|
[self.itype_calla, AM_REGISTER], # 0100 CALLA Rdst
|
|
[self.itype_calla, AM_INDEXED ], # 0101 CALLA X(Rdst)
|
|
[self.itype_calla, AM_INDIRECT], # 0110 CALLA @Rdst
|
|
[self.itype_calla, AM_AUTOINC ], # 0111 CALLA @Rdst+
|
|
[self.itype_calla, AM_ABS20 ], # 1000 CALLA &abs20
|
|
[self.itype_calla, AM_SYM20 ], # 1001 CALLA sym20 (imm20+PC)
|
|
[self.itype_null, -1 ], # 1010 ----
|
|
[self.itype_calla, AM_IMM20 ], # 1011 CALLA #imm20
|
|
[self.itype_null, -1 ], # 1100 ----
|
|
[self.itype_null, -1 ], # 1101 ----
|
|
[self.itype_null, -1 ], # 1110 ----
|
|
[self.itype_null, -1 ], # 1111 ----
|
|
]
|
|
row = tbl[opc]
|
|
insn.itype = row[0]
|
|
if row[1] != -1:
|
|
self.fill_op(insn, insn.Op1, Rdst, row[1], DLEN_AWORD, True, False)
|
|
insn.auxpref = AUX_AX
|
|
|
|
# ----------------------------------------------------------------------
|
|
def decode_430x_pushm(self, insn, w):
|
|
# MSP430X PUSHM/POPM Instructions
|
|
# 15 10 98 7 4 3 0
|
|
# 000101 00 n-1 dst PUSHM.A #n,Rdst
|
|
# 000101 01 n-1 dst PUSHM.W #n,Rdst
|
|
# 000101 10 n-1 dst-n+1 POPM.A #n, Rdst
|
|
# 000101 11 n-1 dst-n+1 POPM.W #n, Rdst
|
|
opc = BITS(w, 7, 4)
|
|
Rdst = BITS(w, 3, 0)
|
|
|
|
ispop = BIT(w, 9)
|
|
insn.itype = [self.itype_pushm, self.itype_popm] [ispop]
|
|
n = BITS(w, 7, 4) + 1
|
|
Rdst = BITS(w, 3, 0)
|
|
if ispop:
|
|
Rdst += n - 1
|
|
insn.Op1.type = o_imm
|
|
insn.Op1.dtype = dt_byte
|
|
insn.Op1.value = n
|
|
isw = BIT(w, 8)
|
|
BW = 0 if isw else 2
|
|
self.fill_op(insn, insn.Op2, Rdst, AM_REGISTER, BW, False, False)
|
|
insn.auxpref = [AUX_A, AUX_WORD] [isw]
|
|
|
|
# ----------------------------------------------------------------------
|
|
# does operand match tuple m? (type, value)
|
|
def match_op(self, op, m):
|
|
if m == None:
|
|
return True
|
|
if op.type != m[0]:
|
|
return False
|
|
if op.type == o_imm:
|
|
return op.value == m[1]
|
|
elif op.type in [o_reg, o_phrase]:
|
|
return op.reg == m[1]
|
|
else:
|
|
return false
|
|
|
|
# ----------------------------------------------------------------------
|
|
# replace some instructions by simplified mnemonics ("emulated" in TI terms)
|
|
def simplify(self, insn):
|
|
# source mnemonic mapped to a list of matches:
|
|
# match function, new mnemonic, new operand
|
|
maptbl = {
|
|
self.itype_addc: [
|
|
# addc #0, dst -> adc dst
|
|
[ lambda: is_imm_op(insn.Op1, 0), self.itype_adc, 2 ],
|
|
# addc dst, dst -> rlc dst
|
|
[ lambda: same_op(insn.Op1, insn.Op2), self.itype_rlc, 1 ],
|
|
],
|
|
self.itype_addcx: [
|
|
# addcx #0, dst -> adcx dst
|
|
[ lambda: is_imm_op(insn.Op1, 0), self.itype_adcx, 2 ],
|
|
# addcx dst, dst -> rlcx dst
|
|
[ lambda: same_op(insn.Op1, insn.Op2), self.itype_rlcx, 1 ],
|
|
],
|
|
self.itype_mov: [
|
|
# mov #0, R3 -> nop
|
|
[ lambda: is_imm_op(insn.Op1, 0) and insn.Op2.is_reg(self.ireg_R3), self.itype_nop, 0 ],
|
|
# mov #0, dst -> clr dst
|
|
[ lambda: is_imm_op(insn.Op1, 0), self.itype_clr, 2 ],
|
|
# mov @SP+, PC -> ret
|
|
[ lambda: is_autoinc(insn.Op1, self.ireg_SP) and insn.Op2.is_reg(self.ireg_PC), self.itype_ret, 0 ],
|
|
# mov @SP+, dst -> pop dst
|
|
[ lambda: is_autoinc(insn.Op1, self.ireg_SP), self.itype_pop, 2 ],
|
|
# mov dst, PC -> br dst
|
|
[ lambda: self.is_movpc(insn), self.itype_br, 1 ],
|
|
],
|
|
self.itype_movx: [
|
|
# movx #0, dst -> clrx dst
|
|
[ lambda: is_imm_op(insn.Op1, 0), self.itype_clrx, 2 ],
|
|
# movx @SP+, dst -> popx dst
|
|
[ lambda: is_autoinc(insn.Op1, self.ireg_SP), self.itype_popx, 2 ],
|
|
],
|
|
self.itype_mova: [
|
|
# mova #0, dst -> clra dst
|
|
[ lambda: is_imm_op(insn.Op1, 0), self.itype_clra, 2 ],
|
|
# mova @SP+, PC -> reta
|
|
[ lambda: is_autoinc(insn.Op1, self.ireg_SP) and insn.Op2.is_reg(self.ireg_PC), self.itype_reta, 0 ],
|
|
# mova @SP+, dst -> popa dst
|
|
[ lambda: is_autoinc(insn.Op1, self.ireg_SP), self.itype_popa, 2 ],
|
|
# mova dst, PC -> bra dst
|
|
[ lambda: insn.Op2.is_reg(self.ireg_PC), self.itype_bra, 1 ],
|
|
],
|
|
self.itype_bic: [
|
|
# bic #1, SR -> clrc
|
|
[ lambda: is_imm_op(insn.Op1, 1) and insn.Op2.is_reg(self.ireg_SR), self.itype_clrc, 0 ],
|
|
# bic #2, SR -> clrz
|
|
[ lambda: is_imm_op(insn.Op1, 2) and insn.Op2.is_reg(self.ireg_SR), self.itype_clrz, 0 ],
|
|
# bic #4, SR -> clrn
|
|
[ lambda: is_imm_op(insn.Op1, 4) and insn.Op2.is_reg(self.ireg_SR), self.itype_clrn, 0 ],
|
|
# bic #8, SR -> dint
|
|
[ lambda: is_imm_op(insn.Op1, 8) and insn.Op2.is_reg(self.ireg_SR), self.itype_dint, 0 ],
|
|
],
|
|
self.itype_bis: [
|
|
# bis #1, SR -> setc
|
|
[ lambda: is_imm_op(insn.Op1, 1) and insn.Op2.is_reg(self.ireg_SR), self.itype_setc, 0 ],
|
|
# bis #2, SR -> setz
|
|
[ lambda: is_imm_op(insn.Op1, 2) and insn.Op2.is_reg(self.ireg_SR), self.itype_setz, 0 ],
|
|
# bis #4, SR -> setn
|
|
[ lambda: is_imm_op(insn.Op1, 4) and insn.Op2.is_reg(self.ireg_SR), self.itype_setn, 0 ],
|
|
# bis #8, SR -> eint
|
|
[ lambda: is_imm_op(insn.Op1, 8) and insn.Op2.is_reg(self.ireg_SR), self.itype_eint, 0 ],
|
|
],
|
|
self.itype_dadd: [
|
|
# dadd #0, dst -> dadc dst
|
|
[ lambda: is_imm_op(insn.Op1, 0), self.itype_dadc, 2 ],
|
|
],
|
|
self.itype_daddx: [
|
|
# daddx #0, dst -> dadcx dst
|
|
[ lambda: is_imm_op(insn.Op1, 0), self.itype_dadcx, 2 ],
|
|
],
|
|
self.itype_sub: [
|
|
# sub #1, dst -> dec dst
|
|
[ lambda: is_imm_op(insn.Op1, 1), self.itype_dec, 2 ],
|
|
# sub #2, dst -> decd dst
|
|
[ lambda: is_imm_op(insn.Op1, 2), self.itype_decd, 2 ],
|
|
],
|
|
self.itype_subx: [
|
|
# subx #1, dst -> decx dst
|
|
[ lambda: is_imm_op(insn.Op1, 1), self.itype_decx, 2 ],
|
|
# subx #2, dst -> decdx dst
|
|
[ lambda: is_imm_op(insn.Op1, 2), self.itype_decdx, 2 ],
|
|
],
|
|
self.itype_suba: [
|
|
# suba #2, dst -> decda dst
|
|
[ lambda: is_imm_op(insn.Op1, 2), self.itype_decda, 2 ],
|
|
],
|
|
self.itype_subc: [
|
|
# subc #0, dst -> sbc dst
|
|
[ lambda: is_imm_op(insn.Op1, 0), self.itype_sbc, 2 ],
|
|
],
|
|
self.itype_subcx: [
|
|
# subcx #0, dst -> sbcx dst
|
|
[ lambda: is_imm_op(insn.Op1, 0), self.itype_sbcx, 2 ],
|
|
],
|
|
self.itype_add: [
|
|
# add #1, dst -> inc dst
|
|
[ lambda: is_imm_op(insn.Op1, 1), self.itype_inc, 2 ],
|
|
# add #2, dst -> incd dst
|
|
[ lambda: is_imm_op(insn.Op1, 2), self.itype_incd, 2 ],
|
|
# add dst, dst -> rla dst
|
|
[ lambda: same_op(insn.Op1, insn.Op2), self.itype_rla, 1 ],
|
|
],
|
|
self.itype_adda: [
|
|
# adda #2, dst -> incda dst
|
|
[ lambda: is_imm_op(insn.Op1, 2), self.itype_incda, 2 ],
|
|
],
|
|
self.itype_addx: [
|
|
# addx #1, dst -> incx dst
|
|
[ lambda: is_imm_op(insn.Op1, 1), self.itype_incx, 2 ],
|
|
# addx #2, dst -> incdx dst
|
|
[ lambda: is_imm_op(insn.Op1, 2), self.itype_incdx, 2 ],
|
|
# addx dst, dst -> rlax dst
|
|
[ lambda: same_op(insn.Op1, insn.Op2), self.itype_rlax, 1 ],
|
|
],
|
|
self.itype_xor: [
|
|
# xor #-1, dst -> inv dst
|
|
[ lambda: is_imm_op(insn.Op1, -1), self.itype_inv, 2 ],
|
|
],
|
|
self.itype_xorx: [
|
|
# xorx #-1, dst -> invx dst
|
|
[ lambda: is_imm_op(insn.Op1, -1), self.itype_invx, 2 ],
|
|
],
|
|
self.itype_cmp: [
|
|
# cmp #0, dst -> tst dst
|
|
[ lambda: is_imm_op(insn.Op1, 0), self.itype_tst, 2 ],
|
|
],
|
|
self.itype_cmpx: [
|
|
# cmpx #0, dst -> tstx dst
|
|
[ lambda: is_imm_op(insn.Op1, 0), self.itype_tstx, 2 ],
|
|
],
|
|
self.itype_cmpa: [
|
|
# cmpa #0, dst -> tsta dst
|
|
[ lambda: is_imm_op(insn.Op1, 0), self.itype_tsta, 2 ],
|
|
],
|
|
}
|
|
|
|
# instructions which should have no suffix
|
|
nosuff = [self.itype_ret, self.itype_reta, self.itype_br, self.itype_bra,
|
|
self.itype_clrc, self.itype_clrn, self.itype_clrz, self.itype_dint,
|
|
self.itype_eint, self.itype_clrc, self.itype_nop, self.itype_pop,
|
|
self.itype_popa, self.itype_setc, self.itype_setn, self.itype_setz, ]
|
|
|
|
if insn.itype in maptbl:
|
|
for m in maptbl[insn.itype]:
|
|
if m[0]():
|
|
# matched instruction; replace the itype
|
|
insn.itype = m[1]
|
|
if m[2] == 0:
|
|
# remove the operands
|
|
insn.Op1.type = o_void
|
|
elif m[2] == 2:
|
|
# replace first operand with the second
|
|
insn.Op1.assign(insn.Op2)
|
|
# remove the second operand, if any
|
|
insn.Op2.type = o_void
|
|
# remove suffix if necessary
|
|
if insn.itype in nosuff and insn.auxpref == AUX_WORD:
|
|
insn.auxpref = 0
|
|
break
|
|
|
|
# ----------------------------------------------------------------------
|
|
def ev_ana_insn(self, insn):
|
|
"""
|
|
Decodes an instruction into 'insn'.
|
|
Returns: insn.size (=the size of the decoded instruction) or zero
|
|
"""
|
|
if (insn.ea & 1) != 0:
|
|
return 0
|
|
w = insn.get_next_word()
|
|
extw = None
|
|
if BITS(w, 15, 11) == 0b00011:
|
|
# operand extension word
|
|
extw = w
|
|
w = insn.get_next_word()
|
|
if BITS(w, 15, 10) == 0b000100:
|
|
self.decode_format_II(insn, w, extw)
|
|
elif BITS(w, 15, 10) == 0b000101:
|
|
if extw: return 0
|
|
self.decode_430x_pushm(insn, w)
|
|
elif BITS(w, 15, 13) == 1: # 001
|
|
if extw: return 0
|
|
self.decode_jump(insn, w)
|
|
elif BITS(w, 15, 12) == 0:
|
|
if extw: return 0
|
|
self.decode_430x_mova(insn, w)
|
|
else:
|
|
self.decode_format_I(insn, w, extw)
|
|
|
|
self.simplify(insn)
|
|
return insn.itype != self.itype_null
|
|
|
|
# ----------------------------------------------------------------------
|
|
def init_instructions(self):
|
|
Instructions = []
|
|
i = 0
|
|
for x in self.instruc:
|
|
if x['name'] != '':
|
|
setattr(self, 'itype_' + x['name'], i)
|
|
else:
|
|
setattr(self, 'itype_null', i)
|
|
i += 1
|
|
|
|
# icode of the last instruction + 1
|
|
self.instruc_end = len(self.instruc)
|
|
|
|
# Icode of return instruction. It is ok to give any of possible return
|
|
# instructions
|
|
self.icode_return = self.itype_reti
|
|
|
|
# ----------------------------------------------------------------------
|
|
def init_registers(self):
|
|
"""This function parses the register table and creates corresponding ireg_XXX constants"""
|
|
|
|
# Registers definition
|
|
self.reg_names = [
|
|
# General purpose registers
|
|
"PC", # R0
|
|
"SP", # R1
|
|
"SR", # R2, CG1
|
|
"R3", # CG2
|
|
"R4",
|
|
"R5",
|
|
"R6",
|
|
"R7",
|
|
"R8",
|
|
"R9",
|
|
"R10",
|
|
"R11",
|
|
"R12",
|
|
"R13",
|
|
"R14",
|
|
"R15",
|
|
# Fake segment registers
|
|
"CS",
|
|
"DS"
|
|
]
|
|
|
|
# Create the ireg_XXXX constants
|
|
for i in range(len(self.reg_names)):
|
|
setattr(self, 'ireg_' + self.reg_names[i], i)
|
|
|
|
# Segment register information (use virtual CS and DS registers if your
|
|
# processor doesn't have segment registers):
|
|
self.reg_first_sreg = self.ireg_CS
|
|
self.reg_last_sreg = self.ireg_DS
|
|
|
|
# number of CS register
|
|
self.reg_code_sreg = self.ireg_CS
|
|
|
|
# number of DS register
|
|
self.reg_data_sreg = self.ireg_DS
|
|
|
|
# ----------------------------------------------------------------------
|
|
def __init__(self):
|
|
processor_t.__init__(self)
|
|
self.init_instructions()
|
|
self.init_registers()
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Every processor module script must provide this function.
|
|
# It should return a new instance of a class derived from processor_t
|
|
def PROCESSOR_ENTRY():
|
|
return msp430_processor_t()
|