class DataPanel(QSplitter): def __init__(self, app): super(DataPanel, self).__init__(app) self.app = app self.data = {} self.setOrientation(Qt.Horizontal) self._key_list_model = QStandardItemModel(0, 1) self.key_lists = DwarfListView(parent=self.app) self.key_lists.setHeaderHidden(True) self.key_lists.setModel(self._key_list_model) self.key_lists.doubleClicked.connect(self.list_item_double_clicked) self.key_lists.setContextMenuPolicy(Qt.CustomContextMenu) self.key_lists.customContextMenuRequested.connect( self._on_context_menu) self.addWidget(self.key_lists) self.editor = QPlainTextEdit() self.addWidget(self.editor) self.hex_view = HexEditor(self.app) self.hex_view.setVisible(False) self.addWidget(self.hex_view) #self.setStretchFactor(0, 8) self.setStretchFactor(1, 4) def clear(self): self._key_list_model.clear() self.editor.setPlainText('') self.hex_view.clear_panel() def append_data(self, data_type, key, text_data): if key not in self.data: self._key_list_model.appendRow([QStandardItem(key)]) self.data[key] = [data_type, text_data] def list_item_double_clicked(self, item): item = self._key_list_model.itemFromIndex(item) if self.data[item.text()][0] == 'plain': self.hex_view.setVisible(False) self.editor.setVisible(True) self.editor.setPlainText(self.data[item.text()][1]) else: self.editor.setVisible(False) self.hex_view.setVisible(True) self.hex_view.bytes_per_line = 16 self.hex_view.set_data(self.data[item.text()][1]) def _on_context_menu(self, pos): context_menu = QMenu(self) index = self.key_lists.indexAt(pos).row() if index != -1: context_menu.addAction('Clear', self.clear) global_pt = self.key_lists.mapToGlobal(pos) context_menu.exec(global_pt)
class WatchersPanel(QWidget): """ WatcherPanel Signals: onItemSelected(addr_str) - item dblclicked onItemAddClick Constants: MEMORY_ACCESS_READ = 1 MEMORY_ACCESS_WRITE = 2 MEMORY_ACCESS_EXECUTE = 4 MEMORY_WATCH_SINGLESHOT = 8 """ MEMORY_ACCESS_READ = 1 MEMORY_ACCESS_WRITE = 2 MEMORY_ACCESS_EXECUTE = 4 MEMORY_WATCH_SINGLESHOT = 8 onItemDoubleClicked = pyqtSignal(int, name='onItemDoubleClicked') onItemAdded = pyqtSignal(int, name='onItemAdded') onItemRemoved = pyqtSignal(int, name='onItemRemoved') def __init__(self, parent=None): # pylint: disable=too-many-statements super(WatchersPanel, self).__init__(parent=parent) self._app_window = parent if self._app_window.dwarf is None: print('Watcherpanel created before Dwarf exists') return self._uppercase_hex = True self.setAutoFillBackground(True) # connect to dwarf self._app_window.dwarf.onWatcherAdded.connect(self._on_watcher_added) self._app_window.dwarf.onWatcherRemoved.connect( self._on_watcher_removed) # setup our model self._watchers_model = QStandardItemModel(0, 5) self._watchers_model.setHeaderData(0, Qt.Horizontal, 'Address') self._watchers_model.setHeaderData(1, Qt.Horizontal, 'R') self._watchers_model.setHeaderData(1, Qt.Horizontal, Qt.AlignCenter, Qt.TextAlignmentRole) self._watchers_model.setHeaderData(2, Qt.Horizontal, 'W') self._watchers_model.setHeaderData(2, Qt.Horizontal, Qt.AlignCenter, Qt.TextAlignmentRole) self._watchers_model.setHeaderData(3, Qt.Horizontal, 'X') self._watchers_model.setHeaderData(3, Qt.Horizontal, Qt.AlignCenter, Qt.TextAlignmentRole) self._watchers_model.setHeaderData(4, Qt.Horizontal, 'S') self._watchers_model.setHeaderData(4, Qt.Horizontal, Qt.AlignCenter, Qt.TextAlignmentRole) # setup ui v_box = QVBoxLayout(self) v_box.setContentsMargins(0, 0, 0, 0) self.list_view = DwarfListView() self.list_view.setModel(self._watchers_model) self.list_view.header().setSectionResizeMode(0, QHeaderView.Stretch) self.list_view.header().setSectionResizeMode( 1, QHeaderView.ResizeToContents | QHeaderView.Fixed) self.list_view.header().setSectionResizeMode( 2, QHeaderView.ResizeToContents | QHeaderView.Fixed) self.list_view.header().setSectionResizeMode( 3, QHeaderView.ResizeToContents | QHeaderView.Fixed) self.list_view.header().setSectionResizeMode( 4, QHeaderView.ResizeToContents | QHeaderView.Fixed) self.list_view.header().setStretchLastSection(False) self.list_view.doubleClicked.connect(self._on_item_dblclick) self.list_view.setContextMenuPolicy(Qt.CustomContextMenu) self.list_view.customContextMenuRequested.connect(self._on_contextmenu) v_box.addWidget(self.list_view) #header = QHeaderView(Qt.Horizontal, self) h_box = QHBoxLayout() h_box.setContentsMargins(5, 2, 5, 5) btn1 = QPushButton(QIcon(utils.resource_path('assets/icons/plus.svg')), '') btn1.setFixedSize(20, 20) btn1.clicked.connect(self._on_additem_clicked) btn2 = QPushButton(QIcon(utils.resource_path('assets/icons/dash.svg')), '') btn2.setFixedSize(20, 20) btn2.clicked.connect(self.delete_items) btn3 = QPushButton( QIcon(utils.resource_path('assets/icons/trashcan.svg')), '') btn3.setFixedSize(20, 20) btn3.clicked.connect(self.clear_list) h_box.addWidget(btn1) h_box.addWidget(btn2) h_box.addSpacerItem( QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Preferred)) h_box.addWidget(btn3) # header.setLayout(h_box) # header.setFixedHeight(25) # v_box.addWidget(header) v_box.addLayout(h_box) # create a centered dot icon _section_width = self.list_view.header().sectionSize(2) self._new_pixmap = QPixmap(_section_width, 20) self._new_pixmap.fill(Qt.transparent) painter = QPainter(self._new_pixmap) rect = QRect((_section_width * 0.5), 0, 20, 20) painter.setBrush(QColor('#666')) painter.setPen(QColor('#666')) painter.drawEllipse(rect) self._dot_icon = QIcon(self._new_pixmap) # shortcuts shortcut_add = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_W), self._app_window, self._on_additem_clicked) shortcut_add.setAutoRepeat(False) self.setLayout(v_box) # ************************************************************************ # **************************** Properties ******************************** # ************************************************************************ @property def uppercase_hex(self): """ Addresses displayed lower/upper-case """ return self._uppercase_hex @uppercase_hex.setter def uppercase_hex(self, value): """ Addresses displayed lower/upper-case value - bool or str 'upper', 'lower' """ if isinstance(value, bool): self._uppercase_hex = value elif isinstance(value, str): self._uppercase_hex = (value == 'upper') # ************************************************************************ # **************************** Functions ********************************* # ************************************************************************ def do_addwatcher_dlg(self, ptr=None): # pylint: disable=too-many-branches """ Shows AddWatcherDialog """ watcher_dlg = AddWatcherDialog(self, ptr) if watcher_dlg.exec_() == QDialog.Accepted: mem_r = watcher_dlg.acc_read.isChecked() mem_w = watcher_dlg.acc_write.isChecked() mem_x = watcher_dlg.acc_execute.isChecked() mem_s = watcher_dlg.singleshot.isChecked() ptr = watcher_dlg.text_field.toPlainText() if ptr: if isinstance(ptr, str): if ptr.startswith('0x') or ptr.startswith('#'): ptr = utils.parse_ptr(ptr) else: try: ptr = int(ptr, 10) except ValueError: pass # int now? if not isinstance(ptr, int): try: ptr = int( self._app_window.dwarf.dwarf_api( 'evaluatePtr', ptr), 16) except ValueError: ptr = 0 if ptr == 0: return if not self._app_window.dwarf.dwarf_api( 'isValidPointer', ptr): return else: return mem_val = 0 if mem_r: mem_val |= self.MEMORY_ACCESS_READ if mem_w: mem_val |= self.MEMORY_ACCESS_WRITE if mem_x: mem_val |= self.MEMORY_ACCESS_EXECUTE if mem_s: mem_val |= self.MEMORY_WATCH_SINGLESHOT self.add_address(ptr, mem_val, from_api=False) # return [ptr, mem_val] def add_address(self, ptr, flags, from_api=False): """ Adds Address to display ptr - str or int flags - int """ if isinstance(ptr, str): ptr = utils.parse_ptr(ptr) if not isinstance(flags, int): try: flags = int(flags, 10) except ValueError: flags = 3 if not from_api: # function was called directly so add it to dwarf if not self._app_window.dwarf.is_address_watched(ptr): self._app_window.dwarf.dwarf_api('addWatcher', [ptr, flags]) return # show header self.list_view.setHeaderHidden(False) # create items to add if self._uppercase_hex: str_frmt = '0x{0:X}' else: str_frmt = '0x{0:x}' addr = QStandardItem() addr.setText(str_frmt.format(ptr)) read = QStandardItem() write = QStandardItem() execute = QStandardItem() singleshot = QStandardItem() if flags & self.MEMORY_ACCESS_READ: read.setIcon(self._dot_icon) if flags & self.MEMORY_ACCESS_WRITE: write.setIcon(self._dot_icon) if flags & self.MEMORY_ACCESS_EXECUTE: execute.setIcon(self._dot_icon) if flags & self.MEMORY_WATCH_SINGLESHOT: singleshot.setIcon(self._dot_icon) # add items as new row on top self._watchers_model.insertRow( 0, [addr, read, write, execute, singleshot]) def remove_address(self, ptr, from_api=False): """ Remove Address from List """ if isinstance(ptr, str): ptr = utils.parse_ptr(ptr) if not from_api: # called somewhere so remove watcher in dwarf too self._app_window.dwarf.dwarf_api('removeWatcher', ptr) return str_frmt = '' if self._uppercase_hex: str_frmt = '0x{0:X}'.format(ptr) else: str_frmt = '0x{0:x}'.format(ptr) model = self.list_view.model() for item in range(model.rowCount()): if str_frmt == model.item(item).text(): model.removeRow(item) def delete_items(self): """ Delete selected Items """ model = self.list_view.model() index = self.list_view.selectionModel().currentIndex().row() if index != -1: ptr = model.item(index, 0).text() self.remove_address(ptr) def clear_list(self): """ Clear the List """ model = self.list_view.model() # go through all items and tell it gets removed for item in range(model.rowCount()): ptr = model.item(item, 0).text() self.remove_address(ptr) if model.rowCount() > 0: # something was wrong it should be empty model.removeRows(0, model.rowCount()) # ************************************************************************ # **************************** Handlers ********************************** # ************************************************************************ def _on_contextmenu(self, pos): index = self.list_view.indexAt(pos).row() glbl_pt = self.list_view.mapToGlobal(pos) context_menu = QMenu(self) context_menu.addAction('Add watcher', self._on_additem_clicked) if index != -1: context_menu.addSeparator() context_menu.addAction( 'Copy address', lambda: utils.copy_hex_to_clipboard( self._watchers_model.item(index, 0).text())) context_menu.addAction( 'Jump to address', lambda: self._app_window.jump_to_address( self._watchers_model.item(index, 0).text())) context_menu.addAction( 'Delete watcher', lambda: self.remove_address( self._watchers_model.item(index, 0).text())) context_menu.exec_(glbl_pt) def _on_item_dblclick(self, model_index): row = self._watchers_model.itemFromIndex(model_index).row() if row != -1: ptr = self._watchers_model.item(row, 0).text() self.onItemDoubleClicked.emit(ptr) def _on_additem_clicked(self): if self._app_window.dwarf.pid == 0: return self.do_addwatcher_dlg() def _on_watcher_added(self, ptr, flags): """ Callback from Dwarf after Watcher is added """ ptr = utils.parse_ptr(ptr) # add to watcherslist self.add_address(ptr, flags, from_api=True) self.onItemAdded.emit(ptr) def _on_watcher_removed(self, ptr): """ Callback from Dwarf after watcher is removed """ ptr = utils.parse_ptr(ptr) # remove from list self.remove_address(ptr, from_api=True) self.onItemRemoved.emit(ptr)
class QDebugPanel(QMainWindow): def __init__(self, app, flags=None): super(QDebugPanel, self).__init__(flags) self.setDockOptions(QMainWindow.AnimatedDocks | QMainWindow.AllowNestedDocks) self.app = app self.q_settings = app.q_settings self.functions_list = DwarfListView() self.functions_list_model = QStandardItemModel(0, 1) self.functions_list_model.setHeaderData(0, Qt.Horizontal, '') self.functions_list.setModel(self.functions_list_model) self.functions_list.setHeaderHidden(True) self.functions_list.doubleClicked.connect(self._function_double_clicked) self.dock_functions_list = QDockWidget('Functions', self) self.dock_functions_list.setObjectName('functions') self.dock_functions_list.setWidget(self.functions_list) self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_functions_list) self.app.debug_view_menu.addAction(self.dock_functions_list.toggleViewAction()) self.memory_panel_range = None self.disassembly_panel_range = None screen_size = QtWidgets.QDesktopWidget().screenGeometry(-1) m_width = screen_size.width() self.memory_panel = HexEditor(self.app) self.memory_panel.debug_panel = self self.memory_panel.dataChanged.connect(self.on_memory_modified) self.disassembly_panel = DisassemblyView(self.app) self.disassembly_panel.debug_panel = self self.dock_memory_panel = QDockWidget('Memory', self) self.dock_memory_panel.setWidget(self.memory_panel) self.dock_memory_panel.setObjectName('memory') self.dock_disassembly_panel = QDockWidget('Disassembly', self) self.dock_disassembly_panel.setWidget(self.disassembly_panel) self.dock_disassembly_panel.setObjectName('disassembly') self.addDockWidget(Qt.RightDockWidgetArea, self.dock_memory_panel) self.addDockWidget(Qt.RightDockWidgetArea, self.dock_disassembly_panel) if m_width >= 1920: self.splitDockWidget(self.dock_memory_panel, self.dock_disassembly_panel, Qt.Horizontal) else: self.tabifyDockWidget(self.dock_memory_panel, self.dock_disassembly_panel) self.restoreUiState() def restoreUiState(self): ui_state = self.q_settings.value('dwarf_debug_ui_state') if ui_state: self.restoreGeometry(ui_state) window_state = self.q_settings.value('dwarf_debug_ui_window') if window_state: self.restoreState(window_state) def closeEvent(self, event): self.q_settings.setValue('dwarf_debug_ui_state', self.saveGeometry()) self.q_settings.setValue('dwarf_debug_ui_window', self.saveState()) def showEvent(self, event): main_width = self.size().width() new_widths = [] new_widths.append(main_width * .1) new_widths.append(main_width * .4) new_widths.append(main_width * .5) self.resizeDocks([ self.dock_functions_list, self.dock_memory_panel, self.dock_disassembly_panel ], new_widths, Qt.Horizontal) return super().showEvent(event) def update_functions(self, functions_list=None): if functions_list is None: functions_list = {} self.functions_list_model.setRowCount(0) for module_info_base in self.app.dwarf.database.modules_info: module_info = self.app.dwarf.database.modules_info[module_info_base] if len(module_info.functions) > 0: self.functions_list.show() for function in module_info.functions: functions_list[function.name] = function.address for function_name in sorted(functions_list.keys()): function_addr = functions_list[function_name] item = QStandardItem(function_name.replace('.', '_')) item.setData(function_addr, Qt.UserRole + 2) self.functions_list_model.appendRow([item]) def _function_double_clicked(self, model_index): item = self.functions_list_model.itemFromIndex(model_index) address = item.data(Qt.UserRole + 2) self.jump_to_address(address, view=DEBUG_VIEW_DISASSEMBLY) def on_context_setup(self): self.memory_panel.on_context_setup() def on_memory_modified(self, pos, length): data_pos = self.memory_panel.base + pos data = self.memory_panel.data[pos:pos + length] data = [data[0]] # todo: strange js part if self.dwarf.dwarf_api('writeBytes', [data_pos, data]): pass else: utils.show_message_box('Failed to write Memory') def raise_memory_panel(self): self.dock_memory_panel.raise_() def raise_disassembly_panel(self): self.dock_disassembly_panel.raise_() 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)) def _apply_range(self, address, view=DEBUG_VIEW_MEMORY): self.update_functions() if view == DEBUG_VIEW_MEMORY: self.memory_panel.set_data( self.memory_panel_range.data, base=self.memory_panel_range.base, focus_address=address) if not self.dock_memory_panel.isVisible(): self.dock_memory_panel.show() self.raise_memory_panel() if self.disassembly_panel_range is None: self.disassembly_panel_range = self.memory_panel_range self.disassembly_panel.apply_range(self.disassembly_panel_range) elif view == DEBUG_VIEW_DISASSEMBLY: self.disassembly_panel.apply_range(self.disassembly_panel_range) if not self.dock_disassembly_panel.isVisible(): self.dock_disassembly_panel.show() self.raise_disassembly_panel() if self.memory_panel_range is None: self.memory_panel_range = self.disassembly_panel_range self.memory_panel.set_data( self.memory_panel_range.data, base=self.memory_panel_range.base, focus_address=address) def is_address_in_view(self, view, address): if view == DEBUG_VIEW_MEMORY: if self.memory_panel_range is not None: ptr_exists = self.memory_panel.base <= address <= self.memory_panel.base + len(self.memory_panel.data) if ptr_exists: self.memory_panel.caret.position = address - self.memory_panel.base return True elif view == DEBUG_VIEW_DISASSEMBLY: if self.disassembly_panel_range is not None: line_index_for_address = self.disassembly_panel.get_line_for_address(address) if line_index_for_address >= 0: self.disassembly_panel.verticalScrollBar().setValue(line_index_for_address) return True return False def on_cm_jump_to_address(self, view=DEBUG_VIEW_MEMORY): ptr, _ = InputDialog.input_pointer(self.app) if ptr > 0: self.jump_to_address(ptr, view=view) 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)