Пример #1
0
    def read_memory(self, ptr, length=0):
        if self._reading_memory:
            return 1

        if self._range is None:
            self._range = Range(self._app_window.dwarf)

        self._reading_memory = True
        self._range.init_async(ptr, length=length, cb=self._on_range_initialized)
        return 0
Пример #2
0
 def dump_data(self, address, _len):
     def _dump(dwarf_range):
         if address + _len > dwarf_range.tail:
             self.display_error('length is higher than range size')
         else:
             data = dwarf_range.data[address:address + _len]
             if data is not None:
                 from PyQt5.QtWidgets import QFileDialog
                 _file = QFileDialog.getSaveFileName(self.app)
                 with open(_file[0], 'wb') as f:
                     f.write(data)
     Range.build_or_get(self.app.dwarf, address, cb=_dump)
Пример #3
0
    def jump_to_address(self, address, view=DEBUG_VIEW_MEMORY):
        address = utils.parse_ptr(address)

        if view == DEBUG_VIEW_MEMORY:
            if self.memory_panel_range is not None:
                if self.is_address_in_view(view, address):
                    return

            self.memory_panel_range = \
                Range.build_or_get(self.app.dwarf, address, cb=lambda x: self._apply_range(address, view=view))
        elif view == DEBUG_VIEW_DISASSEMBLY:
            if self.disassembly_panel_range is not None:
                if self.is_address_in_view(view, address):
                    return

            self.disassembly_panel_range = \
                Range.build_or_get(self.app.dwarf, address, cb=lambda x: self._apply_range(address, view=view))
Пример #4
0
    def _apply_context(self, context, manual=False):
        # update current context tid
        # this should be on top as any further api from js needs to be executed on that thread
        reason = context['reason']
        is_initial_hook = reason == -1
        if manual or (self.dwarf.context_tid and not is_initial_hook):
            self.dwarf.context_tid = context['tid']

        if 'context' in context:
            if not manual:
                self.threads.add_context(context)

            is_java = context['is_java']
            if is_java:
                if self.java_explorer_panel is None:
                    self._create_ui_elem('jvm-debugger')
                self.context_panel.set_context(context['ptr'], 1,
                                               context['context'])
                self.java_explorer_panel._set_handle_arg(-1)
                self.show_main_tab('jvm-debugger')
            else:
                self.context_panel.set_context(context['ptr'], 0,
                                               context['context'])

                if reason == 2:
                    # native on load
                    if self.memory_panel.range is None:
                        base = context['moduleBase']
                        self.show_main_tab('memory')
                        self.memory_panel.read_memory(base)
                else:
                    if 'pc' in context['context']:
                        if 'disassembly' not in self._ui_elems or manual:
                            from lib.types.range import Range
                            _range = Range(self.dwarf)
                            _range.init_with_address(
                                int(context['context']['pc']['value'], 16))

                            self._disassemble_range(_range)

        if 'backtrace' in context:
            self.backtrace_panel.set_backtrace(context['backtrace'])
Пример #5
0
class DisassemblyView(QAbstractScrollArea):
    onDisassemble = pyqtSignal(object, name='onDisassemble')
    onShowMemoryRequest = pyqtSignal(str, int, name='onShowMemoryRequest')
    onDisasmViewKeyPressEvent = pyqtSignal(int, int, name='onDisasmViewKeyPressEvent')

    def __init__(self, parent=None):
        super(DisassemblyView, self).__init__(parent=parent)

        _prefs = Prefs()
        self._uppercase_hex = (_prefs.get('dwarf_ui_hexstyle', 'upper').lower() == 'upper')

        self._app_window = parent
        self._reading_memory = False

        self.setAutoFillBackground(True)

        self._app_window.dwarf.onApplyContext.connect(self.on_arch_changed)

        # setting font
        self.font = utils.get_os_monospace_font()
        self.font.setFixedPitch(True)
        self.setFont(self.font)

        self._char_width = QFontMetricsF(self.font).width('#')  # self.fontMetrics().width("#")
        if (self._char_width % 1) < .5:
            self.font.setLetterSpacing(QFont.AbsoluteSpacing, -(self._char_width % 1.0))
            self._char_width -= self._char_width % 1.0
        else:
            self.font.setLetterSpacing(QFont.AbsoluteSpacing, 1.0 - (self._char_width % 1.0))
            self._char_width += 1.0 - (self._char_width % 1.0)

        self._char_height = self.fontMetrics().height()
        self._base_line = self.fontMetrics().ascent()

        self._history = []
        self._lines = []
        self._range = None
        self._longest_bytes = 0
        self._longest_mnemonic = 0

        self._running_disasm = False
        self.capstone_arch = 0
        self.capstone_mode = 0
        self.keystone_arch = 0
        self.keystone_mode = 0
        self.on_arch_changed()

        self._ctrl_colors = {
            'background': QColor('#181818'),
            'foreground': QColor('#666'),
            'jump_arrows': QColor('#444'),
            'jump_arrows_hover': QColor('#ef5350'),
            'divider': QColor('#666'),
            'line': QColor('#111'),
            'selection_fg': QColor(Qt.white),
            'selection_bg': QColor('#ef5350')
        }

        self._jump_color = QColor('#39a')
        self._header_height = 0
        self._ver_spacing = 2

        self._dash_pen = QPen(self._ctrl_colors['jump_arrows'], 2.0, Qt.DashLine)
        self._solid_pen = QPen(self._ctrl_colors['jump_arrows'], 2.0, Qt.SolidLine)
        self._line_pen = QPen(self._ctrl_colors['divider'], 0, Qt.SolidLine)

        self._breakpoint_linewidth = 5
        self._jumps_width = 100

        self.setMouseTracking(True)
        self.current_jump = -1
        self._current_line = -1

        self._display_jumps = True
        self._follow_jumps = True

        self.pos = 0

        # hacky way to let plugins hook this and inject menu actions
        self.menu_extra_menu_hooks = []
        """
        this is one more way for allowing plugin hooks and perform additional operation on the range object
        """
        self.run_default_disassembler = True

    # ************************************************************************
    # **************************** Properties ********************************
    # ************************************************************************
    @property
    def display_jumps(self):
        return self._display_jumps

    @display_jumps.setter
    def display_jumps(self, value):
        if isinstance(value, bool):
            self._display_jumps = value
            if self._display_jumps:
                self._jumps_width = 100
            else:
                self._jumps_width = 0

    @property
    def follow_jumps(self):
        return self._follow_jumps

    @follow_jumps.setter
    def follow_jumps(self, value):
        if isinstance(value, bool):
            self._follow_jumps = value

    @pyqtProperty('QColor', designable=True)
    def background(self):
        return self._ctrl_colors['background']

    @background.setter
    def background(self, value):
        self._ctrl_colors['background'] = value

    @pyqtProperty('QColor', designable=True)
    def foreground(self):
        return self._ctrl_colors['foreground']

    @foreground.setter
    def foreground(self, value):
        self._ctrl_colors['foreground'] = QColor(value)

    @pyqtProperty('QColor', designable=True)
    def divider(self):
        return self._ctrl_colors['divider']

    @divider.setter
    def divider(self, value):
        self._ctrl_colors['divider'] = QColor(value)

    @pyqtProperty('QColor', designable=True)
    def jump_arrows(self):
        return self._ctrl_colors['jump_arrows']

    @jump_arrows.setter
    def jump_arrows(self, value):
        self._ctrl_colors['jump_arrows'] = QColor(value)

    @pyqtProperty('QColor', designable=True)
    def jump_arrows_hover(self):
        return self._ctrl_colors['jump_arrows_hover']

    @jump_arrows_hover.setter
    def jump_arrows_hover(self, value):
        self._ctrl_colors['jump_arrows_hover'] = QColor(value)

    @pyqtProperty('QColor', designable=True)
    def line(self):
        return self._ctrl_colors['line']

    @line.setter
    def line(self, value):
        self._ctrl_colors['line'] = QColor(value)

    # ************************************************************************
    # **************************** Functions *********************************
    # ************************************************************************

    def add_instruction(self, instruction):
        self._lines.append(instruction)
        self.adjust()

    def disassemble(self, dwarf_range, num_instructions=0):
        if self._running_disasm:
            return

        self.onDisassemble.emit(dwarf_range)

        if self.run_default_disassembler:
            self.start_disassemble(dwarf_range, num_instructions=num_instructions)

    def start_disassemble(self, dwarf_range, num_instructions=0):
        self._running_disasm = True
        self._app_window.show_progress('disassembling...')

        self._lines.clear()
        self.viewport().update()

        if len(self._history) == 0 or self._history[len(self._history) - 1] != dwarf_range.start_address:
            self._history.append(dwarf_range.start_address)
            if len(self._history) > 25:
                self._history.pop(0)

        self._longest_bytes = 0
        try:
            capstone = Cs(self.capstone_arch, self.capstone_mode)
            capstone.detail = True
        except CsError:
            self._running_disasm = False
            print('[DisasmView] failed to initialize capstone with %d, %d' % (self.capstone_arch, self.capstone_mode))
            return

        self._range = dwarf_range
        self.disasm_thread = DisassembleThread(self._app_window)
        self.disasm_thread._num_instructions = num_instructions
        self.disasm_thread._range = self._range
        self.disasm_thread._dwarf = self._app_window.dwarf
        self.disasm_thread._capstone = capstone
        self.disasm_thread.onFinished.connect(self._on_disasm_finished)
        self.disasm_thread.start(QThread.HighestPriority)

    def _on_disasm_finished(self, instructions):
        if isinstance(instructions, list):
            self._lines = instructions
            self.adjust()

        self._running_disasm = False
        self._app_window.hide_progress()

    def resizeEvent(self, event):
        super().resizeEvent(event)
        self.adjust()

    def adjust(self):
        for line in self._lines:
            if line:
                if len(line.bytes) > self._longest_bytes:
                    self._longest_bytes = len(line.bytes)
                if len(line.mnemonic) > self._longest_mnemonic:
                    self._longest_mnemonic = len(line.mnemonic)
        self.verticalScrollBar().setRange(0, len(self._lines) - self.visible_lines() + 1)
        self.verticalScrollBar().setPageStep(self.visible_lines())
        self.viewport().update()

    def visible_lines(self):
        """ returns number of lines that fits viewport
        """
        height = self.viewport().height()
        height -= self._header_height + self._char_height + self._ver_spacing
        num_lines = int(ceil(height / (self._char_height + self._ver_spacing)))
        return num_lines + 1

    def pixel_to_line(self, screen_x, screen_y):
        """ helper
        """
        coord_x, coord_y = self.pixel_to_data(screen_x, screen_y)  # pylint: disable=unused-variable
        return coord_y

    def pixel_to_data(self, screen_x, screen_y):
        """ pixel to data coords
        """
        if screen_x < 0:
            screen_x = 0

        top_gap = self._header_height + self._char_height + self._ver_spacing
        data_x = int(ceil(screen_x / int(self._char_width)))
        data_y = int(
            ceil((screen_y - top_gap) /
                 (self._ver_spacing + self._char_height)))
        return (data_x, data_y)

    def read_memory(self, ptr, length=0):
        if self._reading_memory:
            return 1

        if self._range is None:
            self._range = Range(self._app_window.dwarf)

        self._reading_memory = True
        self._range.init_async(ptr, length=length, cb=self._on_range_initialized)
        return 0

    # ************************************************************************
    # **************************** Drawing ***********************************
    # ************************************************************************
    def paint_jumps(self, painter):
        # TODO: order by distance
        painter.setRenderHint(QPainter.HighQualityAntialiasing)
        jump_list = [x.address for x in self._lines[self.pos:self.pos + self.visible_lines()] if x.is_jump or x.is_call]
        #jump_targets = [x.jump_address for x in self._lines[self.pos:self.pos + self.visible_lines()] if x.address in jump_list]

        jump_targets = []

        for x in self._lines[self.pos:self.pos + self.visible_lines()]:
            if x.address in jump_list:
                if x.is_call:
                    jump_targets.append(x.call_address)
                elif x.is_jump:
                    jump_targets.append(x.jump_address)

        drawing_pos_x = self._jumps_width - 10

        for index, line in enumerate(self._lines[self.pos:self.pos + self.visible_lines()]):
            if line.address in jump_list:  # or line.address in jump_targets:
                if line.address == self.current_jump:
                    self._solid_pen.setColor(self._ctrl_colors['jump_arrows_hover'])
                    self._dash_pen.setColor(self._ctrl_colors['jump_arrows_hover'])
                else:
                    self._solid_pen.setColor(self._ctrl_colors['jump_arrows'])
                    self._dash_pen.setColor(self._ctrl_colors['jump_arrows'])
                if line.id == X86_INS_JMP or line.id == X86_INS_CALL:
                    painter.setPen(self._solid_pen)
                else:
                    painter.setPen(self._dash_pen)
                drawing_pos_y = (index + 1) * (self._char_height + self._ver_spacing)
                drawing_pos_y -= self._base_line - (self._char_height * 0.5)

                skip = False

                if line.address not in jump_targets:
                    painter.drawLine(drawing_pos_x + 2, drawing_pos_y, self._jumps_width, drawing_pos_y)
                    if line.is_jump:
                        _address = line.jump_address
                    elif line.is_call:
                        _address = line.call_address
                    if _address in jump_targets:
                        entry1 = [x for x in self._lines if x.address == line.address]
                        entry2 = [x for x in self._lines if x.address == _address]
                        if entry1 and entry2:
                            skip = True
                            pos2 = (self._lines.index(entry1[0]) - self._lines.index(entry2[0])) * (
                                    self._char_height + self._ver_spacing)
                            painter.drawLine(drawing_pos_x, drawing_pos_y - pos2, drawing_pos_x, drawing_pos_y)
                            painter.drawLine(drawing_pos_x, drawing_pos_y - pos2, 100, drawing_pos_y - pos2)
                            arrow = QPolygon()
                            arrow.append(QPoint(100, drawing_pos_y - pos2))
                            arrow.append(QPoint(100 - 8, drawing_pos_y - pos2 - 4))
                            arrow.append(QPoint(100 - 8, drawing_pos_y - pos2 + 4))
                            if line.address == self.current_jump:
                                painter.setBrush(self._ctrl_colors['jump_arrows_hover'])
                                painter.setPen(self._ctrl_colors['jump_arrows_hover'])
                            else:
                                painter.setBrush(self._ctrl_colors['jump_arrows'])
                                painter.setPen(self._ctrl_colors['jump_arrows'])
                            painter.drawPolygon(arrow)
                else:
                    skip = True

                if not skip:
                    if line.is_jump:
                        _address = line.jump_address
                    elif line.is_call:
                        _address = line.call_address
                    if line.address > _address:
                        if line.id == X86_INS_JMP or line.id == X86_INS_CALL:
                            painter.setPen(self._solid_pen)
                        else:
                            painter.setPen(self._dash_pen)
                        painter.drawLine(drawing_pos_x, 5, drawing_pos_x, drawing_pos_y)
                        arrow = QPolygon()
                        arrow.append(QPoint(drawing_pos_x, 0))
                        arrow.append(QPoint(drawing_pos_x + 4, 8))
                        arrow.append(QPoint(drawing_pos_x - 4, 8))
                        if line.address == self.current_jump:
                            painter.setBrush(self._ctrl_colors['jump_arrows_hover'])
                            painter.setPen(Qt.NoPen)
                        else:
                            painter.setBrush(self._ctrl_colors['jump_arrows'])
                            painter.setPen(Qt.NoPen)
                        painter.drawPolygon(arrow)
                    elif line.address < _address:
                        if line.id == X86_INS_JMP or line.id == X86_INS_CALL:
                            painter.setPen(self._solid_pen)
                        else:
                            painter.setPen(self._dash_pen)
                        painter.drawLine(drawing_pos_x, drawing_pos_y, drawing_pos_x, self.viewport().height() - 5)
                        arrow = QPolygon()
                        arrow.append(QPoint(drawing_pos_x, self.viewport().height()))
                        arrow.append(QPoint(drawing_pos_x + 4, self.viewport().height() - 8))
                        arrow.append(QPoint(drawing_pos_x - 4, self.viewport().height() - 8))
                        if line.address == self.current_jump:
                            painter.setBrush(self._ctrl_colors['jump_arrows_hover'])
                            painter.setPen(Qt.NoPen)
                        else:
                            painter.setBrush(self._ctrl_colors['jump_arrows'])
                            painter.setPen(Qt.NoPen)
                        painter.drawPolygon(arrow)

                drawing_pos_x -= 10
                if drawing_pos_x < 0:
                    break

    def paint_line(self, painter, num_line, line):
        painter.setPen(self._ctrl_colors['foreground'])
        drawing_pos_x = self._jumps_width + self._breakpoint_linewidth + int(self._char_width)
        drawing_pos_y = num_line * (self._char_height + self._ver_spacing)
        drawing_pos_y += self._header_height

        if not line:  # empty line from emu
            return

        num = self._app_window.dwarf.pointer_size * 2
        str_fmt = '{0:08x}'
        if num > 8:
            str_fmt = '{0:016x}'

        if self._uppercase_hex:
            str_fmt = str_fmt.replace('x', 'X')

        painter.drawText(drawing_pos_x, drawing_pos_y, str_fmt.format(line.address))

        is_watched = False
        is_hooked = False

        if self._app_window.dwarf.is_address_watched(line.address):
            is_watched = True

        if line.address in self._app_window.dwarf.hooks:
            is_hooked = True

        if is_watched or is_hooked:
            if is_watched:
                height = self._char_height
                y_pos = drawing_pos_y
                y_pos -= self._base_line - (self._char_height * 0.5)
                y_pos += (self._char_height * 0.5)
                if is_hooked:
                    y_pos -= (self._char_height * 0.5)
                    height *= 0.5
                painter.fillRect(self._jumps_width, y_pos - height, self._breakpoint_linewidth, height,
                                 QColor('greenyellow'))
            if is_hooked:
                height = self._char_height
                y_pos = drawing_pos_y
                y_pos -= self._base_line - (self._char_height * 0.5)
                y_pos += (self._char_height * 0.5)
                if is_watched:
                    height *= 0.5
                painter.fillRect(self._jumps_width, y_pos - height, self._breakpoint_linewidth, height,
                                 QColor('crimson'))

        drawing_pos_x = self._jumps_width + self._breakpoint_linewidth + int(self._char_width) + 1 + int(
            self._char_width)
        drawing_pos_x += (len(str_fmt.format(line.address)) * int(self._char_width))

        painter.setPen(QColor('#444'))
        drawing_pos_x += int(self._char_width)
        for byte in line.bytes:
            painter.drawText(drawing_pos_x, drawing_pos_y, '{0:02x}'.format(byte))
            drawing_pos_x += int(self._char_width) * 3

        drawing_pos_x = self._jumps_width + self._breakpoint_linewidth + (
                (self._app_window.dwarf.pointer_size * 2) * int(self._char_width)) + (self._longest_bytes + 2) * (
                                int(self._char_width) * 3)
        painter.setPen(QColor('#39c'))
        painter.drawText(drawing_pos_x, drawing_pos_y, line.mnemonic)
        if line.is_jump or line.is_call:
            painter.setPen(self._jump_color)
        else:
            painter.setPen(self._ctrl_colors['foreground'])

        drawing_pos_x += (self._longest_mnemonic + 1) * int(self._char_width)
        if line.operands and not line.is_jump and not line.is_call:
            ops_str = line.op_str.split(', ', len(line.operands) - 1)
            a = 0
            for op in line.operands:
                if op.type == CS_OP_IMM:
                    painter.setPen(QColor('#ff5500'))
                elif op.type == 1:
                    painter.setPen(QColor('#82c300'))
                else:
                    painter.setPen(self._ctrl_colors['foreground'])

                painter.drawText(drawing_pos_x, drawing_pos_y, ops_str[a])
                drawing_pos_x += len(ops_str[a] * int(self._char_width))

                if len(line.operands) > 1 and a < len(line.operands) - 1:
                    painter.setPen(self._ctrl_colors['foreground'])
                    painter.drawText(drawing_pos_x, drawing_pos_y, ', ')
                    drawing_pos_x += 2 * int(self._char_width)
                # if ops_str[a].startswith('0x') and not line.string:
                #    line.string = '{0:d}'.format(int(ops_str[a], 16))
                # drawing_pos_x += (len(ops_str[a]) + 1) * self._char_width
                a += 1
        else:
            if self._follow_jumps and (line.is_jump or line.is_call):
                if line.is_jump:
                    _address = line.jump_address
                elif line.is_call:
                    _address = line.call_address
                if _address < line.address:
                    painter.drawText(drawing_pos_x, drawing_pos_y, line.op_str + ' ▲')
                elif _address > line.address:
                    painter.drawText(drawing_pos_x, drawing_pos_y, line.op_str + ' ▼')
                drawing_pos_x += (len(line.op_str) + 3) * int(self._char_width)
            else:
                painter.drawText(drawing_pos_x, drawing_pos_y, line.op_str)
                drawing_pos_x += (len(line.op_str) + 1) * int(self._char_width)

        if line.symbol_name:
            painter.drawText(drawing_pos_x, drawing_pos_y, '(' + line.symbol_name + ')')
            drawing_pos_x += (len(line.symbol_name) + 1) * int(self._char_width)

        if line.string and not line.is_jump and not line.is_call:
            painter.setPen(QColor('#aaa'))
            painter.drawText(drawing_pos_x, drawing_pos_y, ' ; "' + line.string + '"')

    def paint_wait(self, painter):
        """ paint wait popup
        """
        brdr_col = QColor('#444')
        back_col = QColor('#222')
        painter.setPen(QColor('#888'))
        back_rect = self.viewport().rect()
        back_rect.setWidth(back_rect.width() * .2)
        back_rect.setHeight(back_rect.height() * .1)
        screen_x = self.viewport().width() * .5
        screen_x -= back_rect.width() * .5
        screen_y = self.viewport().height() * .5
        screen_y -= back_rect.height() * .5
        painter.fillRect(screen_x - 1, screen_y - 1,
                         back_rect.width() + 2,
                         back_rect.height() + 2, brdr_col)
        painter.fillRect(screen_x, screen_y, back_rect.width(),
                         back_rect.height(), back_col)
        qtext_align = QTextOption(Qt.AlignVCenter | Qt.AlignHCenter)
        text_rect = QRectF(screen_x + 10, screen_y + 10,
                           back_rect.width() - 20,
                           back_rect.height() - 20)
        painter.drawText(text_rect, "Disassembling...", option=qtext_align)

    def paintEvent(self, event):
        if not self.isVisible():
            return

        painter = QPainter(self.viewport())

        if self._running_disasm:
            return self.paint_wait(painter)

        if not self._lines:
            return

        self.pos = self.verticalScrollBar().value()

        # fill background
        painter.fillRect(0, 0, self.viewport().width(), self.viewport().height(), self._ctrl_colors['background'])

        if self._display_jumps:
            painter.setPen(self._ctrl_colors['foreground'])
            drawing_pos_x = self._jumps_width
            self.paint_jumps(painter)
            painter.fillRect(drawing_pos_x, 0, self._breakpoint_linewidth, self.viewport().height(),
                             self._ctrl_colors['jump_arrows'])

        for i, line in enumerate(self._lines[self.pos:self.pos + self.visible_lines()]):
            if i > self.visible_lines():
                break

            if i == self._current_line:
                y_pos = self._header_height + (i * (self._char_height + self._ver_spacing))
                y_pos += (self._char_height * 0.5)
                y_pos -= self._ver_spacing
                painter.fillRect(self._jumps_width + self._breakpoint_linewidth, y_pos - 1, self.viewport().width(),
                                 self._char_height + 2, self._ctrl_colors['line'])
            self.paint_line(painter, i + 1, line)

        painter.setPen(self._line_pen)
        painter.setBrush(Qt.NoBrush)
        drawing_pos_x = self._jumps_width + self._breakpoint_linewidth + int(self._char_width) + int(self._char_width)
        drawing_pos_x += ((self._app_window.dwarf.pointer_size * 2) * int(self._char_width))

        painter.fillRect(drawing_pos_x, 0, 1, self.viewport().height(), self._ctrl_colors['divider'])

    # ************************************************************************
    # **************************** Handlers **********************************
    # ************************************************************************
    def keyPressEvent(self, event):
        self.onDisasmViewKeyPressEvent.emit(event.key(), event.modifiers)
        if event.key() == Qt.Key_Backspace or event.key() == Qt.Key_Escape:
            if len(self._history) > 1:
                self._history.pop(len(self._history) - 1)
                self.read_memory(self._history[len(self._history) - 1])
        elif event.key() == Qt.Key_G and event.modifiers() & Qt.ControlModifier:  # ctrl+g
            self._on_cm_jump_to_address()
        elif event.key() == Qt.Key_M and event.modifiers() & Qt.ControlModifier:  # ctrl+m
            self._on_switch_mode()
        elif event.key() == Qt.Key_P and event.modifiers() & Qt.ControlModifier:  # ctrl+p
            pass  # patch instruction
        elif event.key() == Qt.Key_B and event.modifiers() & Qt.ControlModifier:  # ctrl+b
            pass  # patch bytes
        else:
            # dispatch those to super
            super().keyPressEvent(event)

    def on_arch_changed(self, context=None):
        if self._app_window.dwarf.arch == 'arm64':
            self.capstone_arch = CS_ARCH_ARM64
            self.capstone_mode = CS_MODE_LITTLE_ENDIAN
        elif self._app_window.dwarf.arch == 'arm':
            self.capstone_arch = CS_ARCH_ARM
            context = self._app_window.dwarf.current_context()
            self.capstone_mode = CS_MODE_ARM
            if context is not None and context.is_native_context:
                if context.pc.thumb:
                    self.capstone_mode = CS_MODE_THUMB
        elif self._app_window.dwarf.arch == 'ia32':
            self.capstone_arch = CS_ARCH_X86
            self.capstone_mode = CS_MODE_32
        elif self._app_window.dwarf.arch == 'x64':
            self.capstone_arch = CS_ARCH_X86
            self.capstone_mode = CS_MODE_64
        if self._app_window.dwarf.keystone_installed:
            import keystone.keystone_const as ks
            if self._app_window.dwarf.arch == 'arm64':
                self.keystone_arch = ks.KS_ARCH_ARM64
                self.keystone_mode = ks.KS_MODE_LITTLE_ENDIAN
            elif self._app_window.dwarf.arch == 'arm':
                self.keystone_arch = ks.KS_ARCH_ARM
                self.keystone_mode = ks.KS_MODE_ARM
            elif self._app_window.dwarf.arch == 'ia32':
                self.keystone_arch = ks.KS_ARCH_X86
                self.keystone_mode = ks.KS_MODE_32
            elif self._app_window.dwarf.arch == 'x64':
                self.keystone_arch = ks.KS_ARCH_X86
                self.keystone_mode = ks.KS_MODE_64

    def _on_range_initialized(self, range_, result):
        self._reading_memory = False
        if result <= 0:
            self.disassemble(self._range)

    def mouseDoubleClickEvent(self, event):
        loc_x = event.pos().x()
        loc_y = event.pos().y()

        index = self.pixel_to_line(loc_x, loc_y)
        if 0 <= index < self.visible_lines():
            if index + self.pos >= len(self._lines):
                return
            left_side = self._breakpoint_linewidth + self._jumps_width
            addr_width = ((self._app_window.dwarf.pointer_size * 2) * int(self._char_width))

            # get instruction
            _instruction = self._lines[index + self.pos]
            if not _instruction or not isinstance(_instruction, Instruction):
                return

            if loc_x > left_side:
                if loc_x < left_side + addr_width:
                    self.onShowMemoryRequest.emit(
                        hex(_instruction.address), len(_instruction.bytes))
                if loc_x > left_side + addr_width:
                    if self._follow_jumps and (_instruction.is_jump or _instruction.is_call):
                        if _instruction.is_jump:
                            new_pos = _instruction.jump_address
                        elif _instruction.is_call:
                            new_pos = _instruction.call_address
                        new_line = [x for x in self._lines if x.address == new_pos]
                        try:
                            new_line = self._lines.index(new_line[0])
                            self.verticalScrollBar().setValue(new_line)
                            # TODO: add highlighting line
                            return
                        except IndexError:
                            pass

                        if new_pos > 0:
                            self.read_memory(new_pos)
                        else:
                            # noone should ever view this
                            print('Error: trying to read from pos 0!')

    # pylint: disable=C0103
    def mouseMoveEvent(self, event):
        """ onmousemove
        """
        loc_x = event.pos().x()
        loc_y = event.pos().y()

        if loc_x > self._breakpoint_linewidth + self._jumps_width:
            self.current_jump = -1
            index = self.pixel_to_line(loc_x, loc_y)

            if 0 <= index < self.visible_lines():
                self._current_line = index
                if index + self.pos < len(self._lines):
                    if isinstance(self._lines[index + self.pos], Instruction):
                        _instruction = self._lines[index + self.pos]
                        if _instruction.is_jump or _instruction.is_call:
                            self.current_jump = self._lines[index + self.pos].address

            # self.viewport().update(0, 0, self._breakpoint_linewidth + self._jumps_width, self.viewport().height())
            y_pos = self._header_height + (index * (self._char_height + self._ver_spacing))
            y_pos += (self._char_height * 0.5)
            y_pos -= self._ver_spacing
            self.viewport().update(0, 0, self.viewport().width(), self.viewport().height())

    def mousePressEvent(self, event):
        # context menu
        if event.button() == Qt.RightButton:
            if self._running_disasm:
                return
            self._on_context_menu(event)

    def _on_context_menu(self, event):
        """ build and show contextmenu
        """
        loc_x = event.pos().x()
        loc_y = event.pos().y()

        context_menu = QMenu()

        context_menu.addAction('Jump to address', self._on_cm_jump_to_address)

        # allow mode switch arm/thumb
        if self.capstone_arch == CS_ARCH_ARM:
            if self.capstone_mode == CS_MODE_THUMB:
                mode_str = 'ARM'
            else:
                mode_str = 'THUMB'
            entry_str = '&Switch to {0} mode'.format(mode_str)
            context_menu.addAction(entry_str, self._on_switch_mode)

        if not self._lines:
            # allow jumpto in empty panel
            glbl_pt = self.mapToGlobal(event.pos())
            context_menu.exec_(glbl_pt)
            return

        context_menu.addSeparator()

        index = self.pixel_to_line(loc_x, loc_y)
        address = -1
        if 0 <= index < self.visible_lines():
            if index + self.pos < len(self._lines):
                if isinstance(self._lines[index + self.pos], Instruction):
                    address = self._lines[index + self.pos].address
                    context_menu.addAction(
                        'Copy address', lambda: utils.copy_hex_to_clipboard(address))

                    context_menu.addSeparator()

                    if self._uppercase_hex:
                        str_fmt = '0x{0:X}'
                    else:
                        str_fmt = '0x{0:x}'
                    addr_str = str_fmt.format(address)
                    if self._app_window.watchers_panel:
                        if self._app_window.dwarf.is_address_watched(address):
                            context_menu.addAction(
                                'Remove watcher', lambda: self._app_window.watchers_panel.remove_address(addr_str))
                        else:
                            context_menu.addAction(
                                'Watch address', lambda: self._app_window.watchers_panel.do_addwatcher_dlg(addr_str))
                    if self._app_window.hooks_panel:
                        if address in self._app_window.dwarf.hooks:
                            context_menu.addAction(
                                'Remove hook', lambda: self._app_window.dwarf.dwarf_api('deleteHook', addr_str))
                        else:
                            context_menu.addAction('Hook address', lambda: self._app_window.dwarf.hook_native(addr_str))

        for fcn in self.menu_extra_menu_hooks:
            try:
                fcn(context_menu, address)
            except Exception as e:
                print('failed to add hook menu: %s' % str(e))

        glbl_pt = self.mapToGlobal(event.pos())
        context_menu.exec_(glbl_pt)

    def _on_switch_mode(self):
        if self._range is None:
            return

        if self._app_window.dwarf.arch == 'arm':
            self._lines.clear()

            if self.capstone_mode == CS_MODE_ARM:
                self.capstone_mode = CS_MODE_THUMB
            else:
                self.capstone_mode = CS_MODE_ARM
            self.disassemble(self._range)

    def _on_cm_jump_to_address(self):
        ptr, _ = InputDialog.input_pointer(self._app_window)
        if ptr > 0:
            self.read_memory(ptr)