"""
summary: list instruction registers

description:
  Shows a list of direct references to a register from the
  current instruction.

level: advanced
"""

import ida_pro
import ida_hexrays
import ida_kernwin
import ida_funcs
import ida_bytes
import ida_lines

def collect_block_xrefs(out, mlist, blk, ins, find_uses):
    p = ins
    while p and not mlist.empty():
        use = blk.build_use_list(p, ida_hexrays.MUST_ACCESS); # things used by the insn
        _def = blk.build_def_list(p, ida_hexrays.MUST_ACCESS); # things defined by the insn
        plst = use if find_uses else _def
        if mlist.has_common(plst):
            if not p.ea in out:
                out.append(p.ea) # this microinstruction seems to use our operand
        mlist.sub(_def)
        p = p.next if find_uses else p.prev


def collect_xrefs(out, ctx, mop, mlist, du, find_uses):
    # first collect the references in the current block
    start = ctx.topins.next if find_uses else ctx.topins.prev;
    collect_block_xrefs(out, mlist, ctx.blk, start, find_uses)

    # then find references in other blocks
    serial = ctx.blk.serial; # block number of the operand
    bc = du[serial]          # chains of that block
    voff = ida_hexrays.voff_t(mop)
    ch = bc.get_chain(voff)   # chain of the operand
    if not ch:
        return # odd
    for bn in ch:
        b = ctx.mba.get_mblock(bn)
        ins = b.head if find_uses else b.tail
        tmp = ida_hexrays.mlist_t()
        tmp.add(mlist)
        collect_block_xrefs(out, tmp, b, ins, find_uses)


class xref_chooser_t(ida_kernwin.Choose):
    def __init__(self, xrefs, t, n, ea, gco):
        ida_kernwin.Choose.__init__(
            self,
            t,
            [["Type", 3], ["Address", 16], ["Instruction", 60]])

        self.xrefs = xrefs
        self.ndefs = n
        self.curr_ea = ea
        self.gco = gco
        self.items = [ self._make_item(idx) for idx in range(len(xrefs)) ]

    def OnGetSize(self):
        return len(self.items)

    def OnGetLine(self, n):
        return self.items[n]

    def _make_item(self, idx):
        ea = self.xrefs[idx]
        both_mask = ida_hexrays.GCO_USE|ida_hexrays.GCO_DEF
        both = (self.gco.flags & both_mask) == both_mask
        if ea == self.curr_ea and both:
            type_str = "use/def"
        elif idx < self.ndefs:
            type_str = "def"
        else:
            type_str = "use"
        insn = ida_lines.generate_disasm_line(ea, ida_lines.GENDSM_REMOVE_TAGS)
        return [type_str, "%08x" % ea, insn]


def show_xrefs(ea, gco, xrefs, ndefs):
    title = "xrefs to %s at %08x" % (gco.name, ea)
    xc = xref_chooser_t(xrefs, title, ndefs, ea, gco)
    i = xc.Show(True)
    if i >= 0:
        ida_kernwin.jumpto(xrefs[i])


if ida_hexrays.init_hexrays_plugin():
    ea = ida_kernwin.get_screen_ea()
    pfn = ida_funcs.get_func(ea)
    w = ida_kernwin.warning
    if pfn:
        F = ida_bytes.get_flags(ea)
        if ida_bytes.is_code(F):
            gco = ida_hexrays.gco_info_t()
            if ida_hexrays.get_current_operand(gco):
                # generate microcode
                hf = ida_hexrays.hexrays_failure_t()
                mbr = ida_hexrays.mba_ranges_t(pfn)
                mba = ida_hexrays.gen_microcode(
                    mbr,
                    hf,
                    None,
                    ida_hexrays.DECOMP_WARNINGS | ida_hexrays.DECOMP_NO_CACHE,
                    ida_hexrays.MMAT_PREOPTIMIZED)
                if mba:
                    merr = mba.build_graph()
                    if merr == ida_hexrays.MERR_OK:
                        ncalls = mba.analyze_calls(ida_hexrays.ACFL_GUESS)
                        if ncalls < 0:
                            print("%08x: failed to determine some calling conventions", pfn.start_ea)
                        mlist = ida_hexrays.mlist_t()
                        if gco.append_to_list(mlist, mba):
                            ctx = ida_hexrays.op_parent_info_t()
                            mop = mba.find_mop(ctx, ea, gco.is_def(), mlist)
                            if mop:
                                xrefs = ida_pro.eavec_t()
                                ndefs = 0
                                graph = mba.get_graph()
                                ud = graph.get_ud(ida_hexrays.GC_REGS_AND_STKVARS)
                                du = graph.get_du(ida_hexrays.GC_REGS_AND_STKVARS)
                                if gco.is_use():
                                    collect_xrefs(xrefs, ctx, mop, mlist, ud, False)
                                    ndefs = xrefs.size()
                                    if ea not in xrefs:
                                        xrefs.append(ea)
                                if gco.is_def():
                                    if ea not in xrefs:
                                        xrefs.append(ea)
                                        ndefs = len(xrefs)
                                    collect_xrefs(xrefs, ctx, mop, mlist, du, True)
                                show_xrefs(ea, gco, xrefs, ndefs)
                            else:
                                w("Could not find the operand in the microcode, sorry")
                        else:
                            w("Failed to represent %s as microcode list" % gco.name)
                    else:
                        w("%08x: %s" % (errea, ida_hexrays.get_merror_desc(merr, mba)))
                else:
                    w("%08x: %s" % (hf.errea, hf.str))
            else:
                w("Could not find a register or stkvar in the current operand")
        else:
            w("Please position the cursor on an instruction")
    else:
        w("Please position the cursor within a function")
else:
    print('vds12: Hex-rays is not available.')

