示例#1
0
class QInstruction(QCachedGraphicsItem):

    GRAPH_ADDR_SPACING = 20
    GRAPH_MNEMONIC_SPACING = 10
    GRAPH_OPERAND_SPACING = 2
    GRAPH_COMMENT_STRING_SPACING = 10

    INTERSPERSE_ARGS = ', '

    LINEAR_INSTRUCTION_OFFSET = 120
    COMMENT_PREFIX = "// "

    def __init__(self,
                 workspace,
                 func_addr,
                 disasm_view,
                 disasm,
                 infodock,
                 insn,
                 out_branch,
                 config,
                 parent=None):
        super().__init__(parent=parent)

        # initialization
        self.workspace = workspace
        self.func_addr = func_addr
        self.disasm_view = disasm_view
        self.disasm = disasm
        self.infodock = infodock
        self.variable_manager = infodock.variable_manager
        self.insn = insn
        self.out_branch = out_branch
        self._config = config

        # all "widgets"
        self._addr = None
        self._mnemonic = None
        self._addr_item: QGraphicsSimpleTextItem = None
        self._mnemonic_item: QGraphicsSimpleTextItem = None
        self._operands: List[QOperand] = []
        self._commas: List[QGraphicsSimpleTextItem] = []
        self._string = None
        self._string_item: Optional[QGraphicsSimpleTextItem] = None
        self._comment = None
        self._comment_items: Optional[
            List[QGraphicsSimpleTextItem]] = None  # one comment per line
        self._legend = None
        self._width = 0
        self._height = 0

        self._init_widgets()

    def contextMenuEvent(
            self,
            event: PySide2.QtWidgets.QGraphicsSceneContextMenuEvent) -> None:
        pass

    def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
        if self.workspace.plugins.handle_click_insn(self, event):
            # stop handling this event if the event has been handled by a plugin
            event.accept()
        elif event.button(
        ) == Qt.LeftButton and QApplication.keyboardModifiers() in (
                Qt.NoModifier, Qt.ControlModifier):
            # toggle selection
            self.infodock.toggle_instruction_selection(
                self.addr,
                insn_pos=self.scenePos(),
                unique=QApplication.keyboardModifiers() != Qt.ControlModifier)
            event.accept()
        elif event.button(
        ) == Qt.RightButton and QApplication.keyboardModifiers(
        ) == Qt.NoModifier:
            if self.addr not in self.infodock.selected_insns:
                self.infodock.toggle_instruction_selection(
                    self.addr, insn_pos=self.scenePos(), unique=True)
            self.disasm_view.instruction_context_menu(self.insn, QCursor.pos())
            event.accept()
        else:
            super().mousePressEvent(event)

    @property
    def addr(self):
        return self.insn.addr

    def _calc_backcolor(self):
        # First we'll check for customizations
        color = self.workspace.plugins.color_insn(self.insn.addr,
                                                  self.selected)
        if color is not None:
            return color

        if self.selected:
            return self._config.disasm_view_node_instruction_selected_background_color

        return None  # None here means transparent, reusing the block color

    @property
    def selected(self):
        """
        If this instruction is selected or not.

        :return:    True if it is selected, False otherwise.
        :rtype:     bool
        """

        return self.infodock.is_instruction_selected(self.addr)

    def clear_cache(self):
        super().clear_cache()
        for obj in self._operands:
            obj.clear_cache()

    def refresh(self):
        self.load_comment()
        self._init_comments_or_string()

        for operand in self._operands:
            operand.refresh()

        self._layout_items_and_update_size()
        self.recalculate_size()

    def get_operand(self, operand_idx):
        if operand_idx < len(self._operands):
            return self._operands[operand_idx]
        return None

    def load_comment(self):
        self._comment = get_comment_for_display(self.workspace.instance.kb,
                                                self.insn.addr)

    def paint(self, painter, option, widget):  # pylint: disable=unused-argument

        painter.setRenderHints(QPainter.Antialiasing
                               | QPainter.SmoothPixmapTransform
                               | QPainter.HighQualityAntialiasing)

        # background color
        backcolor = self._calc_backcolor()
        if backcolor is not None:
            painter.setBrush(backcolor)
            painter.setPen(backcolor)
            painter.drawRect(0, 0, self.width, self.height)

        # any plugin instruction rendering passes
        self.workspace.plugins.draw_insn(self, painter)

    #
    # Private methods
    #

    def _init_widgets(self):

        self.load_comment()
        self._operands.clear()

        # address
        self._addr = "%08x" % self.insn.addr
        self._addr_item = QGraphicsSimpleTextItem(self)
        self._addr_item.setBrush(
            QBrush(self._config.disasm_view_node_address_color))
        self._addr_item.setFont(self._config.disasm_font)
        self._addr_item.setText(self._addr)

        # mnemonic
        self._mnemonic = self.insn.mnemonic.render()[0]
        self._mnemonic_item = QGraphicsSimpleTextItem(self)
        self._mnemonic_item.setFont(self._config.disasm_font)
        self._mnemonic_item.setBrush(
            self._config.disasm_view_node_mnemonic_color)
        self._mnemonic_item.setText(self._mnemonic)

        # operands
        for i, operand in enumerate(self.insn.operands):
            is_branch_target = self.insn.type in (
                'branch', 'call') and i == self.insn.branch_target_operand
            is_indirect_branch = self.insn.branch_type == 'indirect'
            branch_targets = None
            if is_branch_target:
                if self.out_branch is not None:
                    branch_targets = self.out_branch.targets
                else:
                    # it does not create multiple branches. e.g., a call instruction
                    if len(operand.children) == 1 and type(
                            operand.children[0]) is Value:
                        branch_targets = (operand.children[0].val, )
            qoperand = QOperand(self.workspace,
                                self.func_addr,
                                self.disasm_view,
                                self.disasm,
                                self.infodock,
                                self.insn,
                                operand,
                                i,
                                is_branch_target,
                                is_indirect_branch,
                                branch_targets,
                                self._config,
                                parent=self)
            self._operands.append(qoperand)

        # all commas
        for _ in range(len(self._operands) - 1):
            comma = QGraphicsSimpleTextItem(self.INTERSPERSE_ARGS, self)
            comma.setFont(self._config.disasm_font)
            comma.setBrush(self._config.disasm_view_node_mnemonic_color)
            self._commas.append(comma)

        if should_display_string_label(self.workspace.instance.cfg,
                                       self.insn.addr,
                                       self.workspace.instance.project):
            # yes we should display a string label
            self._string = get_string_for_display(
                self.workspace.instance.cfg, self.insn.addr,
                self.workspace.instance.project)
            if self._string is None:
                self._string = "<Unknown>"

        self._init_comments_or_string()

        self._layout_items_and_update_size()

    def _init_comments_or_string(self):

        # remove existing comments or strings
        if self._comment_items:
            for comm in self._comment_items:
                comm: QGraphicsSimpleTextItem
                comm.setParentItem(None)
            self._comment_items = None
        elif self._string_item is not None:
            self._string_item.setParentItem(None)
            self._string_item = None

        # comment or string - comments have precedence
        if self._comment:
            self._string_item = None
            lines = self._comment.split('\n')
            self._comment_items = []
            for line in lines:
                comment = QGraphicsSimpleTextItem(self.COMMENT_PREFIX + line,
                                                  self)
                comment.setFont(self._config.disasm_font)
                comment.setBrush(
                    Qt.darkGreen)  # TODO: Expose it as a setting in Config
                self._comment_items.append(comment)
        elif self._string is not None:
            self._comment_items = None
            self._string_item = QGraphicsSimpleTextItem(self._string, self)
            self._string_item.setFont(self._config.disasm_font)
            self._string_item.setBrush(
                Qt.gray)  # TODO: Expose it as a setting in Config

    def _layout_items_and_update_size(self):

        x, y = 0, 0

        # address
        if self.disasm_view.show_address:
            self._addr_item.setVisible(True)
            self._addr_item.setPos(x, y)
            x += self._addr_item.boundingRect().width(
            ) + self.GRAPH_ADDR_SPACING
        else:
            self._addr_item.setVisible(False)

        # mnemonic
        self._mnemonic_item.setPos(x, y)
        x += self._mnemonic_item.boundingRect().width(
        ) + self.GRAPH_MNEMONIC_SPACING

        # operands and commas
        for operand, comma in zip(self._operands, self._commas):
            operand.setPos(x, y)
            x += operand.boundingRect().width()
            comma.setPos(x, y)
            x += comma.boundingRect().width()

        # the last operand
        if self._operands:
            self._operands[-1].setPos(x, y)
            x += self._operands[-1].boundingRect().width()

        # comments and strings
        if self._comment_items:
            x += self.GRAPH_COMMENT_STRING_SPACING
            max_comment_width = 0
            for comment in self._comment_items:
                comment.setPos(x, y)
                max_comment_width = max(comment.boundingRect().width(),
                                        max_comment_width)
                y += comment.boundingRect().height()
            x += max_comment_width
        elif self._string_item is not None:
            x += self.GRAPH_COMMENT_STRING_SPACING
            self._string_item.setPos(x, y)
            x += self._string_item.boundingRect().width()
            y += self._string_item.boundingRect().height()
        else:
            y = self._mnemonic_item.boundingRect().height()

        # update size
        self._width = x
        self._height = y
        self.recalculate_size()

    def _boundingRect(self):
        return QRectF(0, 0, self._width, self._height)
示例#2
0
class QBlockCode(QCachedGraphicsItem):
    """
    Top-level code widget for a selection of text. Will construct an AST using
    QBlockCodeObj, mirroring the structure associated with the target object.
    This text is then rendered using a QTextDocument, with appropriate styles
    applied to it. Interaction events will be propagated to corresponding
    objects.
    """

    GRAPH_ADDR_SPACING = 20

    addr: int
    _addr_str: str
    obj: QBlockCodeObj
    _config: ConfigurationManager
    disasm_view: 'QDisassemblyBaseControl'
    workspace: 'Workspace'
    infodock: InfoDock
    parent: Any

    def __init__(self,
                 addr: int,
                 obj: QBlockCodeObj,
                 config: ConfigurationManager,
                 disasm_view: 'QDisassemblyBaseControl',
                 workspace: 'Workspace',
                 infodock: InfoDock,
                 parent: Any = None):
        super().__init__(parent=parent)
        self.addr = addr
        self._addr_str = "%08x" % self.addr
        self._addr_item: QGraphicsSimpleTextItem = None
        self.obj = obj
        self._width = 0
        self._height = 0
        self._config = config
        self.parent = parent
        self.workspace = workspace
        self.infodock = infodock
        self._disasm_view = disasm_view
        self._qtextdoc = QTextDocument()
        self._qtextdoc.setDefaultFont(self._config.disasm_font)
        self._qtextdoc.setDocumentMargin(0)

        self._addr_item = QGraphicsSimpleTextItem(self._addr_str, self)
        self._addr_item.setBrush(Conf.disasm_view_node_address_color)
        self._addr_item.setFont(Conf.disasm_font)

        self.update_document()
        self.setToolTip("Address: " + self._addr_str)

        self.refresh()

    def refresh(self):
        self._addr_item.setVisible(self._disasm_view.show_address)
        self._layout_items_and_update_size()

    def update_document(self):
        self._qtextdoc.clear()
        cur = QTextCursor(self._qtextdoc)
        self.obj.render_to_doc(cur)

    def paint(self, painter, option, widget):  #pylint: disable=unused-argument
        self.update_document()
        painter.setRenderHints(QPainter.Antialiasing
                               | QPainter.SmoothPixmapTransform
                               | QPainter.HighQualityAntialiasing)
        painter.setFont(self._config.disasm_font)

        if self.infodock.is_instruction_selected(
                self.addr) or self.obj.should_highlight_line:
            highlight_color = Conf.disasm_view_node_instruction_selected_background_color
            painter.setBrush(highlight_color)
            painter.setPen(highlight_color)
            painter.drawRect(0, 0, self.width, self.height)

        x = 0

        if self._disasm_view.show_address:
            x += self._addr_item.boundingRect().width(
            ) + self.GRAPH_ADDR_SPACING

        painter.translate(QPointF(x, 0))
        self._qtextdoc.drawContents(painter)

    #
    # Event handlers
    #

    def get_obj_for_mouse_event(self, event: QMouseEvent) -> QBlockCodeObj:
        p = event.pos()

        if self._disasm_view.show_address:
            offset = self._addr_item.boundingRect().width(
            ) + self.GRAPH_ADDR_SPACING
            p.setX(p.x() - offset)

        if p.x() >= 0:
            hitpos = self._qtextdoc.documentLayout().hitTest(
                p, Qt.HitTestAccuracy.ExactHit)
            if hitpos >= 0:
                return self.obj.get_hit_obj(hitpos)

        return None

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.infodock.select_instruction(self.addr)

        obj = self.get_obj_for_mouse_event(event)
        if obj is not None:
            obj.mousePressEvent(event)

    def mouseDoubleClickEvent(self, event):
        obj = self.get_obj_for_mouse_event(event)
        if obj is not None:
            obj.mouseDoubleClickEvent(event)

    #
    # Private methods
    #

    def _layout_items_and_update_size(self):
        self.update_document()

        x, y = 0, 0
        if self._disasm_view.show_address:
            self._addr_item.setPos(x, y)
            x += self._addr_item.boundingRect().width(
            ) + self.GRAPH_ADDR_SPACING

        x += self._qtextdoc.size().width()
        y += self._qtextdoc.size().height()
        self._width = x
        self._height = y
        self.recalculate_size()

    def _boundingRect(self):
        return QRectF(0, 0, self._width, self._height)
示例#3
0
class QVariable(QCachedGraphicsItem):

    IDENT_LEFT_PADDING = 5
    OFFSET_LEFT_PADDING = 12

    def __init__(self, workspace, disasm_view, variable, config, parent=None):
        super(QVariable, self).__init__(parent=parent)

        # initialization
        self.workspace = workspace
        self.disasm_view = disasm_view
        self.variable = variable
        self._config = config

        self._variable_name = None
        self._variable_name_item: QGraphicsSimpleTextItem = None
        self._variable_ident = None
        self._variable_ident_item: QGraphicsSimpleTextItem = None
        self._variable_offset = None
        self._variable_offset_item: QGraphicsSimpleTextItem = None

        self._init_widgets()

    #
    # Public methods
    #

    def paint(self, painter, option, widget):  # pylint: disable=unused-argument
        pass

    def refresh(self):
        super(QVariable, self).refresh()

        if self._variable_ident_item is not None:
            self._variable_ident_item.setVisible(
                self.disasm_view.show_variable_identifier)

        self._layout_items_and_update_size()

    #
    # Private methods
    #

    def _init_widgets(self):

        # variable name
        self._variable_name = "" if not self.variable.name else self.variable.name
        self._variable_name_item = QGraphicsSimpleTextItem(
            self._variable_name, self)
        self._variable_name_item.setFont(self._config.disasm_font)
        self._variable_name_item.setBrush(
            Qt.darkGreen)  # TODO: Expose it as a configuration entry in Config

        # variable ident
        self._variable_ident = "<%s>" % ("" if not self.variable.ident else
                                         self.variable.ident)
        self._variable_ident_item = QGraphicsSimpleTextItem(
            self._variable_ident, self)
        self._variable_ident_item.setFont(self._config.disasm_font)
        self._variable_ident_item.setBrush(
            Qt.blue)  # TODO: Expose it as a configuration entry in Config
        self._variable_ident_item.setVisible(
            self.disasm_view.show_variable_identifier)

        # variable offset
        self._variable_offset = "%#x" % self.variable.offset
        self._variable_offset_item = QGraphicsSimpleTextItem(
            self._variable_offset, self)
        self._variable_offset_item.setFont(self._config.disasm_font)
        self._variable_offset_item.setBrush(
            Qt.darkYellow
        )  # TODO: Expose it as a configuration entry in Config

        self._layout_items_and_update_size()

    def _layout_items_and_update_size(self):

        x, y = 0, 0

        # variable name
        self._variable_name_item.setPos(x, y)
        x += self._variable_name_item.boundingRect().width(
        ) + self.IDENT_LEFT_PADDING

        if self.disasm_view.show_variable_identifier:
            # identifier
            x += self.IDENT_LEFT_PADDING
            self._variable_ident_item.setPos(x, y)
            x += self._variable_ident_item.boundingRect().width()

        # variable offset
        x += self.OFFSET_LEFT_PADDING
        self._variable_offset_item.setPos(x, y)
        x += self._variable_offset_item.boundingRect().width()

        self._width = x
        self._height = self._variable_name_item.boundingRect().height()
        self.recalculate_size()

    def _boundingRect(self):
        return QRectF(0, 0, self._width, self._height)