# udis86 - scripts/ud_opcode.py
# 
# Copyright (c) 2009 Vivek Thampi
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without modification, 
# are permitted provided that the following conditions are met:
# 
#     * Redistributions of source code must retain the above copyright notice, 
#       this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright notice, 
#       this list of conditions and the following disclaimer in the documentation 
#       and/or other materials provided with the distribution.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

class UdOpcodeTables:

    TableInfo = {
        'opctbl'    : { 'name' : 'UD_TAB__OPC_TABLE',   'size' : 256 },
        '/sse'      : { 'name' : 'UD_TAB__OPC_SSE',     'size' : 4 },
        '/reg'      : { 'name' : 'UD_TAB__OPC_REG',     'size' : 8 },
        '/rm'       : { 'name' : 'UD_TAB__OPC_RM',      'size' : 8 },
        '/mod'      : { 'name' : 'UD_TAB__OPC_MOD',     'size' : 2 },
        '/m'        : { 'name' : 'UD_TAB__OPC_MODE',    'size' : 3 },
        '/x87'      : { 'name' : 'UD_TAB__OPC_X87',     'size' : 64 },
        '/a'        : { 'name' : 'UD_TAB__OPC_ASIZE',   'size' : 3 },
        '/o'        : { 'name' : 'UD_TAB__OPC_OSIZE',   'size' : 3 },
        '/3dnow'    : { 'name' : 'UD_TAB__OPC_3DNOW',   'size' : 256 },
        'vendor'    : { 'name' : 'UD_TAB__OPC_VENDOR',  'size' : 3 },
    }

    OpcodeTable0 = {
        'type'      : 'opctbl',
        'entries'   : {},
        'meta'      : 'table0'
    }

    OpcExtIndex = {

        # ssef2, ssef3, sse66
        'sse': {
            'none' : '00', 
            'f2'   : '01', 
            'f3'   : '02', 
            '66'   : '03'
        },

        # /mod=
        'mod': {
            '!11'   : '00', 
            '11'    : '01'
        },

        # /m=, /o=, /a=
        'mode': { 
            '16'    : '00', 
            '32'    : '01', 
            '64'    : '02'
        },

        'vendor' : {
            'amd'   : '00',
            'intel' : '01',
            'any'   : '02'
        }
    }

    InsnTable = []
    MnemonicsTable = []

    ThreeDNowTable = {}

    def sizeOfTable( self, t ): 
        return self.TableInfo[ t ][ 'size' ]

    def nameOfTable( self, t ): 
        return self.TableInfo[ t ][ 'name' ]

    #
    # Updates a table entry: If the entry doesn't exist
    # it will create the entry, otherwise, it will walk
    # while validating the path.
    #
    def updateTable( self, table, index, type, meta ):
        if not index in table[ 'entries' ]:
            table[ 'entries' ][ index ] = { 'type' : type, 'entries' : {}, 'meta' : meta } 
        if table[ 'entries' ][ index ][ 'type' ] != type:
            raise NameError( "error: violation in opcode mapping (overwrite) %s with %s." % 
                                ( table[ 'entries' ][ index ][ 'type' ], type) )
        return table[ 'entries' ][ index ]

    class Insn:
        """An abstract type representing an instruction in the opcode map.
        """

        # A mapping of opcode extensions to their representational
        # values used in the opcode map.
        OpcExtMap = {
            '/rm'    : lambda v: "%02x" % int(v, 16),
            '/x87'   : lambda v: "%02x" % int(v, 16),
            '/3dnow' : lambda v: "%02x" % int(v, 16),
            '/reg'   : lambda v: "%02x" % int(v, 16),
            # modrm.mod
            # (!11, 11)    => (00, 01)
            '/mod'   : lambda v: '00' if v == '!11' else '01',
            # Mode extensions:
            # (16, 32, 64) => (00, 01, 02)
            '/o'     : lambda v: "%02x" % (int(v) // 32),
            '/a'     : lambda v: "%02x" % (int(v) // 32),
            '/m'     : lambda v: "%02x" % (int(v) // 32),
            '/sse'   : lambda v: UdOpcodeTables.OpcExtIndex['sse'][v]
        }

        def __init__(self, prefixes, mnemonic, opcodes, operands, vendor):
            self.opcodes  = opcodes
            self.prefixes = prefixes
            self.mnemonic = mnemonic
            self.operands = operands
            self.vendor   = vendor
            self.opcext   = {}

            ssePrefix = None
            if self.opcodes[0] in ('ssef2', 'ssef3', 'sse66'):
                ssePrefix = self.opcodes[0][3:]
                self.opcodes.pop(0)

            # do some preliminary decoding of the instruction type
            # 1byte, 2byte or 3byte instruction?
            self.nByteInsn = 1
            if self.opcodes[0] == '0f': # 2byte
                # 2+ byte opcodes are always disambiguated by an
                # sse prefix, unless it is a 3d now instruction
                # which is 0f 0f ...
                if self.opcodes[1] != '0f' and ssePrefix is None:
                    ssePrefix = 'none'
                if self.opcodes[1] in ('38', '3a'): # 3byte
                    self.nByteInsn = 3
                else:
                    self.nByteInsn = 2
           
            # The opcode that indexes into the opcode table.
            self.opcode = self.opcodes[self.nByteInsn - 1]
            
            # Record opcode extensions
            for opcode in self.opcodes[self.nByteInsn:]:
                arg, val = opcode.split('=')
                self.opcext[arg] = self.OpcExtMap[arg](val)

            # Record sse extension: the reason sse extension is handled 
            # separately is that historically sse was handled as a first
            # class opcode, not as an extension. Now that sse is handled
            # as an extension, we do the manual conversion here, as opposed
            # to modifying the opcode xml file.
            if ssePrefix is not None:
                self.opcext['/sse'] = self.OpcExtMap['/sse'](ssePrefix)

    def parse(self, table, insn):
        index = insn.opcodes[0];
        if insn.nByteInsn > 1:
            assert index == '0f'
            table = self.updateTable(table, index, 'opctbl', '0f')
            index = insn.opcodes[1]

            if insn.nByteInsn == 3:
                table = self.updateTable(table, index, 'opctbl', index)
                index = insn.opcodes[2]

        # Walk down the tree, create levels as needed, for opcode
        # extensions. The order is important, and determines how
        # well the opcode table is packed. Also note, /sse must be
        # before /o, because /sse may consume operand size prefix
        # affect the outcome of /o.
        for ext in ('/mod', '/x87', '/reg', '/rm', '/sse',
                    '/o',   '/a',   '/m',   '/3dnow'):
            if ext in insn.opcext:
                table = self.updateTable(table, index, ext, ext)
                index = insn.opcext[ext]

        # additional table for disambiguating vendor
        if len(insn.vendor):
            table = self.updateTable(table, index, 'vendor', insn.vendor)
            index = self.OpcExtIndex['vendor'][insn.vendor]

        # make leaf node entries
        leaf = self.updateTable(table, index, 'insn', '')

        leaf['mnemonic'] = insn.mnemonic
        leaf['prefixes'] = insn.prefixes
        leaf['operands'] = insn.operands

        # add instruction to linear table of instruction forms
        self.InsnTable.append({ 'prefixes' : insn.prefixes,  
                                'mnemonic' : insn.mnemonic, 
                                'operands' : insn.operands })

        # add mnemonic to mnemonic table
        if not insn.mnemonic in self.MnemonicsTable:
            self.MnemonicsTable.append(insn.mnemonic)


    # Adds an instruction definition to the opcode tables
    def addInsnDef( self, prefixes, mnemonic, opcodes, operands, vendor ):
        insn = self.Insn(prefixes=prefixes,
                    mnemonic=mnemonic,
                    opcodes=opcodes,
                    operands=operands,
                    vendor=vendor)
        self.parse(self.OpcodeTable0, insn)

    def print_table( self, table, pfxs ):
        print("%s   |" % pfxs)
        keys = table[ 'entries' ].keys()
        if ( len( keys ) ):
            keys.sort()
        for idx in keys:
            e = table[ 'entries' ][ idx ]
            if e[ 'type' ] == 'insn':
                print("%s   |-<%s>" % ( pfxs, idx )),
                print("%s %s" % ( e[ 'mnemonic' ], ' '.join( e[ 'operands'] ) ))
            else:
                print("%s   |-<%s> %s" % ( pfxs, idx, e['type'] ))
                self.print_table( e, pfxs + '   |' )

    def print_tree( self ): 
        self.print_table( self.OpcodeTable0, '' )
