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)
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)
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)