diff --git a/dwex/__main__.py b/dwex/__main__.py index aa3574c..54bd083 100644 --- a/dwex/__main__.py +++ b/dwex/__main__.py @@ -1,3 +1,4 @@ +from bisect import bisect_left import sys, os from PyQt6.QtCore import Qt, QModelIndex, QSettings, QUrl, QEvent from PyQt6.QtGui import QFontMetrics, QDesktopServices, QWindow @@ -742,8 +743,13 @@ def on_localsat(self): def on_aranges(self): ara = self.dwarfinfo.get_aranges() if ara: - ArangesDlg(self, ara, self.dwarfinfo).exec() - # TODO: navigate to CU + dlg = ArangesDlg(self, ara, self.dwarfinfo) + if dlg.exec() == QDialog.DialogCode.Accepted and dlg.selected_cu_offset is not None: + di = self.dwarfinfo + i = bisect_left(di._CU_offsets, dlg.selected_cu_offset) + if i < len(di._CU_offsets) and di._CU_offsets[i] == dlg.selected_cu_offset: + die = di._unsorted_CUs[i].get_top_DIE() + self.the_tree.setCurrentIndex(self.tree_model.index_for_die(die)) else: QMessageBox(QMessageBox.Icon.Warning, "DWARF Explorer", "This binary does not have an aranges section.", QMessageBox.StandardButton.Ok, self).show() diff --git a/dwex/aranges.py b/dwex/aranges.py index 09fce37..9f957a8 100644 --- a/dwex/aranges.py +++ b/dwex/aranges.py @@ -1,7 +1,7 @@ from PyQt6.QtCore import Qt, QAbstractTableModel from PyQt6.QtWidgets import * -from .dwarfutil import safe_DIE_name +from .dwarfutil import top_die_file_name headers = ["Start address", "Length", 'CU offset', 'Source name'] @@ -35,28 +35,44 @@ def data(self, index, role): return hex(entry.info_offset) elif col == 3: cu = self.dwarfinfo.get_CU_at(entry.info_offset) - return safe_DIE_name(cu.get_top_DIE(), '?') + return top_die_file_name(cu.get_top_DIE()) + +###################################################################################### + +# TODO: sort by header click class ArangesDlg(QDialog): def __init__(self, win, ara, di): QDialog.__init__(self, win, Qt.WindowType.Dialog) - self.resize(500, 400) + self.selected_cu_offset = False + self.resize(500, 500) ly = QVBoxLayout() self.the_table = QTableView() self.the_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) + self.the_table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) self.the_table.setModel(AraModel(ara, di)) - # self.the_table.doubleClicked.connect(self.on_dclick) + self.the_table.selectionModel().currentChanged.connect(self.on_sel) + self.the_table.doubleClicked.connect(self.navigate_to_index) + self.the_table.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents) ly.addWidget(self.the_table) buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Close, Qt.Orientation.Horizontal, self) - #self.nav_bu = QPushButton("Navigate", self) - #self.nav_bu.clicked.connect(self.on_navigate) - #self.nav_bu.setEnabled(False) - #buttons.addButton(self.nav_bu, QDialogButtonBox.ButtonRole.ApplyRole) + self.nav_bu = QPushButton("Navigate", self) + self.nav_bu.clicked.connect(lambda: self.navigate_to_index(self.the_table.currentIndex())) + self.nav_bu.setEnabled(False) + buttons.addButton(self.nav_bu, QDialogButtonBox.ButtonRole.ApplyRole) buttons.accepted.connect(self.reject) buttons.rejected.connect(self.reject) ly.addWidget(buttons) self.setWindowTitle('Aranges') - self.setLayout(ly) + self.setLayout(ly) + + def on_sel(self, index, prev = None): + self.nav_bu.setEnabled(index.isValid()) + + def navigate_to_index(self, index): + row = index.row() + self.selected_cu_offset = self.the_table.model().entries[row].info_offset + self.done(QDialog.DialogCode.Accepted) diff --git a/dwex/dwarfutil.py b/dwex/dwarfutil.py index defdac0..424199f 100644 --- a/dwex/dwarfutil.py +++ b/dwex/dwarfutil.py @@ -111,6 +111,28 @@ def is_block(form): def DIE_name(die): return die.attributes['DW_AT_name'].value.decode('utf-8', errors='ignore') +# Supports both / and \ - current system separator might not match the system the file came from +# so os.path.basename won't do +def strip_path(filename): + p = filename.rfind("/") + pbsl = filename.rfind("\\") + if pbsl >= 0 and (p < 0 or pbsl > p): + p = pbsl + return filename[p+1:] if p >= 0 else filename + +def top_die_file_name(die, Default = 'N/A'): + if 'DW_AT_name' in die.attributes: + source_name = die.attributes['DW_AT_name'].value.decode('utf-8', errors='ignore') + return strip_path(source_name) + elif 'DW_AT_decl_file' in die.attributes: + val = die.attributes['DW_AT_decl_file'].value + if val > 0: + if die.cu._lineprogram is None: + die.cu._lineprogram = die.dwarfinfo.line_program_for_CU(die.cu) + delta = 1 if die.cu.version < 5 else 0 + return strip_path(die.cu._lineprogram.header.file_entry[val-delta].name.decode('utf-8', errors='ignore')) + return Default + def safe_DIE_name(die, default = ''): return die.attributes['DW_AT_name'].value.decode('utf-8', errors='ignore') if 'DW_AT_name' in die.attributes else default diff --git a/dwex/frames.py b/dwex/frames.py index 60c78eb..be4fcd6 100644 --- a/dwex/frames.py +++ b/dwex/frames.py @@ -159,12 +159,14 @@ def __init__(self, win, cfi, di, regnames): top_pane.addWidget(w) entries.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) + entries.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) top_pane.addWidget(entries) w = QWidget() w.setLayout(top_pane) spl.addWidget(w) details.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) + details.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) #details.doubleClicked.connect(win.on_attribute_dclick) bottom_pane = QVBoxLayout() bottom_pane.setContentsMargins(0, 0, 0, 0) diff --git a/dwex/locals.py b/dwex/locals.py index 37231b8..22be599 100644 --- a/dwex/locals.py +++ b/dwex/locals.py @@ -89,6 +89,8 @@ def __init__(self, win): def reset(cl, di): cl._last_start_address = di._start_address +############################################################################# + class LocalsDlg(LoadedModuleDlgBase): _last_address = '' # Stored as string to allow for blank @@ -126,12 +128,13 @@ def __init__(self, win, di, prefix, regnames, hexadecimal): self.locals = QTableView() self.locals.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) - self.locals.doubleClicked.connect(self.on_dclick) + self.locals.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) + self.locals.doubleClicked.connect(self.navigate_to_index) ly.addWidget(self.locals) buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Close, Qt.Orientation.Horizontal, self) self.nav_bu = QPushButton("Navigate", self) - self.nav_bu.clicked.connect(self.on_navigate) + self.nav_bu.clicked.connect(lambda: self.navigate_to_index(self.locals.currentIndex())) self.nav_bu.setEnabled(False) buttons.addButton(self.nav_bu, QDialogButtonBox.ButtonRole.ApplyRole) buttons.accepted.connect(self.reject) @@ -224,16 +227,11 @@ def on_check(self): #TODO: relocate absolute addresses in expressions except Exception as exc: QMessageBox(QMessageBox.Icon.Critical, "DWARF Explorer", "Unexpected error while analysing the debug information.", QMessageBox.StandardButton.Ok, self).show() - - def on_navigate(self): - row = self.locals.currentIndex().row() - self.selected_die = self.locals.model().data[row][3] - self.done(QDialog.DialogCode.Accepted) - def on_dclick(self, index): + def navigate_to_index(self, index): row = index.row() self.selected_die = self.locals.model().data[row][3] - self.done(QDialog.DialogCode.Accepted) + self.done(QDialog.DialogCode.Accepted) def on_sel(self, index, prev = None): self.nav_bu.setEnabled(index.isValid()) diff --git a/dwex/tree.py b/dwex/tree.py index e5ad4f2..d5ff81c 100644 --- a/dwex/tree.py +++ b/dwex/tree.py @@ -2,28 +2,7 @@ from PyQt6.QtCore import Qt, QAbstractItemModel, QModelIndex from PyQt6.QtGui import QFont, QFontInfo, QBrush from PyQt6.QtWidgets import QApplication, QMessageBox -from .dwarfutil import has_code_location, safe_DIE_name - -# Supports both / and \ - current system separator might not match the system the file came from -# so os.path.basename won't do -def strip_path(filename): - p = filename.rfind("/") - pbsl = filename.rfind("\\") - if pbsl >= 0 and (p < 0 or pbsl > p): - p = pbsl - return filename[p+1:] if p >= 0 else filename - -def top_die_file_name(die): - if 'DW_AT_name' in die.attributes: - source_name = die.attributes['DW_AT_name'].value.decode('utf-8', errors='ignore') - return strip_path(source_name) - elif 'DW_AT_decl_file' in die.attributes: - val = die.attributes['DW_AT_decl_file'].value - if val > 0: - if die.cu._lineprogram is None: - die.cu._lineprogram = die.dwarfinfo.line_program_for_CU(die.cu) - return strip_path(die.cu._lineprogram.header.file_entry[val-1].name.decode('utf-8', errors='ignore')) - return "(no name)" +from .dwarfutil import has_code_location, safe_DIE_name, top_die_file_name def cu_sort_key(cu): return top_die_file_name(cu.get_top_DIE()).lower() diff --git a/dwex/ui.py b/dwex/ui.py index 975f99b..2e7d213 100644 --- a/dwex/ui.py +++ b/dwex/ui.py @@ -130,6 +130,7 @@ def setup_menu(win): win.localsat_menuitem = ana_menu.addAction("Locals at address...") win.localsat_menuitem.setEnabled(False) win.localsat_menuitem.triggered.connect(win.on_localsat) + ana_menu.addSeparator() win.aranges_menuitem = ana_menu.addAction("Aranges...") win.aranges_menuitem.setEnabled(False) win.aranges_menuitem.triggered.connect(win.on_aranges) @@ -236,6 +237,7 @@ def setup_ui(win): rpane = QSplitter(Qt.Orientation.Vertical) die_table = win.die_table = QTableView() die_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) + die_table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) die_table.doubleClicked.connect(win.on_attribute_dclick) rpane.addWidget(die_table) @@ -246,6 +248,7 @@ def setup_ui(win): rbpane.addWidget(details_warning) details_table = win.details_table = QTableView() details_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) + details_table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) rbpane.addWidget(details_table) rbp = QWidget() rbp.setLayout(rbpane)