diff options
| author | Alexander Taylor <alex@vector35.com> | 2025-04-14 11:15:01 -0400 |
|---|---|---|
| committer | Alexander Taylor <alex@vector35.com> | 2025-04-14 14:53:51 -0400 |
| commit | 507935f828188047ee009d2216a2c2d92c8b83fd (patch) | |
| tree | 4247355ef5788809c8e47fc11e345a623f053196 | |
| parent | 37e20cc30d57fff6b78bf6be9be2230a8ff973cc (diff) | |
Fixes for multiple issues in DSC/KC Triage views.
1. Crash on kernel cache image load
2. Duplicate Load Image buttons in kernel cache view
3. Improper selection behavior in both views
4. Address column should no longer resize to be smaller than contents
5. Symbol tables should now be properly sortable
| -rw-r--r-- | view/kernelcache/ui/kctriage.cpp | 179 | ||||
| -rw-r--r-- | view/kernelcache/ui/kctriage.h | 238 | ||||
| -rw-r--r-- | view/sharedcache/ui/dsctriage.cpp | 149 | ||||
| -rw-r--r-- | view/sharedcache/ui/dsctriage.h | 73 | ||||
| -rw-r--r-- | view/sharedcache/ui/symboltable.cpp | 51 | ||||
| -rw-r--r-- | view/sharedcache/ui/symboltable.h | 47 |
6 files changed, 371 insertions, 366 deletions
diff --git a/view/kernelcache/ui/kctriage.cpp b/view/kernelcache/ui/kctriage.cpp index 7b3cf9f0..aa2d7bb1 100644 --- a/view/kernelcache/ui/kctriage.cpp +++ b/view/kernelcache/ui/kctriage.cpp @@ -1,152 +1,14 @@ -#include "globalarea.h" -#include "kctriage.h" -#include "progresstask.h" -#include "ui/fontsettings.h" #include <QMessageBox> #include <QPainter> #include <cmath> +#include "globalarea.h" +#include "kctriage.h" +#include "ui/fontsettings.h" using namespace BinaryNinja; using namespace KernelCacheAPI; -SymbolTableModel::SymbolTableModel(SymbolTableView* parent) - : QAbstractTableModel(parent), m_parent(parent) { - // TODO: Need to implement updating this font if it is changed by the user - m_font = getMonospaceFont(parent); -} - - -int SymbolTableModel::rowCount(const QModelIndex& parent) const { - Q_UNUSED(parent); - return static_cast<int>(m_symbols.size()); -} - - -int SymbolTableModel::columnCount(const QModelIndex& parent) const { - Q_UNUSED(parent); - // We have 3 columns: Address, Name, and Image - return 3; -} - - -QVariant SymbolTableModel::data(const QModelIndex& index, int role) const { - if (!index.isValid() || (role != Qt::DisplayRole && role != Qt::FontRole)) { - return QVariant(); - } - - const KCSymbol& symbol = m_symbols.at(index.row()); - - switch (role) - { - case Qt::DisplayRole: - { - switch (index.column()) { - case 0: // Address column - return QString("0x%1").arg(symbol.address, 0, 16); // Display address as hexadecimal - case 1: // Name column - return QString::fromStdString(symbol.name); - case 2: // Image column - return QString::fromStdString(symbol.image); - default: - return QVariant(); - } - } - case Qt::FontRole: - return m_font; - default: - return QVariant(); - } -} - - -QVariant SymbolTableModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (role != Qt::DisplayRole || orientation != Qt::Horizontal) { - return QVariant(); - } - - switch (section) { - case 0: - return QString("Address"); - case 1: - return QString("Name"); - case 2: - return QString("Image"); - default: - return QVariant(); - } -} - - -void SymbolTableModel::updateSymbols() { - m_symbols = m_parent->m_symbols; - setFilter(m_filter); -} - - -const KCSymbol& SymbolTableModel::symbolAt(int row) const { - return m_symbols.at(row); -} - - -void SymbolTableModel::setFilter(std::string text) -{ - beginResetModel(); - - m_filter = text; - m_symbols.clear(); - - if (!m_filter.empty()) - { - m_symbols.reserve(m_parent->m_symbols.size()); - for (const auto& symbol : m_parent->m_symbols) - if (symbol.name.find(m_filter) != std::string::npos) - m_symbols.push_back(symbol); - m_symbols.shrink_to_fit(); - } - else - { - m_symbols = m_parent->m_symbols; - } - - endResetModel(); -} - - -SymbolTableView::SymbolTableView(QWidget* parent, Ref<KernelCache> cache) - : QTableView(parent), m_model(new SymbolTableModel(this)) { - - // Set up the filter model - setModel(m_model); - - // Configure view settings - horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); - setEditTriggers(QAbstractItemView::NoEditTriggers); - setSelectionBehavior(QAbstractItemView::SelectRows); - setSelectionMode(QAbstractItemView::SingleSelection); - - setSortingEnabled(true); - - BackgroundThread::create(this)->thenBackground([this, cache](){ - // LogInfo("Symbol Search: Loading symbols..."); - m_symbols = cache->LoadAllSymbolsAndWait(); - // LogInfo("Symbol Search: Loaded 0x%zx symbols", m_symbols.size()); - })->thenMainThread([this](){ - m_model->updateSymbols(); - })->start(); -} - - -SymbolTableView::~SymbolTableView() { - delete m_model; -} - - -void SymbolTableView::setFilter(const std::string& filter) { - m_model->setFilter(filter); -} - - KCTriageViewType::KCTriageViewType() : ViewType("KCTriage", "Kernel Cache Triage") {} @@ -194,8 +56,6 @@ KCTriageView::KCTriageView(QWidget* parent, BinaryViewRef data) : QWidget(parent m_layout->addWidget(m_triageTabs); setLayout(m_layout); - // XXX: RefreshData - m_triageTabs->selectWidget(defaultWidget); } @@ -261,18 +121,18 @@ QWidget* KCTriageView::initImageTable() auto loadImageButton = new QPushButton(); connect(loadImageButton, &QPushButton::clicked, [this](bool) { // Collect only visible selected rows - QModelIndexList visibleSelectedRows; + QModelIndexList selected; for (const auto& index : m_imageTable->selectionModel()->selectedRows()) { if (!m_imageTable->isRowHidden(index.row())) { - visibleSelectedRows.append(index); + selected.append(index); } } - if (visibleSelectedRows.empty()) + if (selected.empty()) return; - for (const auto& selection : visibleSelectedRows) { - auto name = selection.data().toString().toStdString(); + for (const auto& selection : selected) { + auto name = m_imageModel->item(selection.row(), 1)->text().toStdString(); WorkerPriorityEnqueue([this, name]() { m_cache->LoadImageWithInstallName(name); }); } }); @@ -284,7 +144,7 @@ QWidget* KCTriageView::initImageTable() }); connect(m_imageTable, &FilterableTableView::activated, this, [=](const QModelIndex& index) { - auto selected = m_imageModel->item(index.row(), 0); + auto selected = m_imageModel->item(index.row(), 1); auto name = selected->text().toStdString(); WorkerPriorityEnqueue([this, name]() { m_cache->LoadImageWithInstallName(name); }); }); @@ -292,12 +152,11 @@ QWidget* KCTriageView::initImageTable() auto loadImageLayout = new QVBoxLayout; loadImageLayout->addWidget(loadImageFilterEdit); loadImageLayout->addWidget(m_imageTable); - loadImageLayout->addWidget(loadImageButton); - auto buttonLayout = new QHBoxLayout; - buttonLayout->addWidget(loadImageButton); - buttonLayout->setAlignment(Qt::AlignLeft); - loadImageLayout->addLayout(buttonLayout); + auto loadImageFooterLayout = new QHBoxLayout; + loadImageFooterLayout->addWidget(loadImageButton); + loadImageFooterLayout->setAlignment(Qt::AlignLeft); + loadImageLayout->addLayout(loadImageFooterLayout); auto loadImageWidget = new QWidget; loadImageWidget->setLayout(loadImageLayout); @@ -306,7 +165,7 @@ QWidget* KCTriageView::initImageTable() m_imageTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - m_imageTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); + m_imageTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); m_imageTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); m_imageTable->setSelectionBehavior(QAbstractItemView::SelectRows); @@ -332,9 +191,6 @@ void KCTriageView::initSymbolTable() m_symbolTable->setFilter(filter.toStdString()); }); - // Apply custom column styling - m_symbolTable->setItemDelegateForColumn(0, new AddressColorDelegate(m_symbolTable)); - auto symbolLayout = new QVBoxLayout; symbolLayout->addWidget(symbolFilterEdit); symbolLayout->addWidget(m_symbolTable); @@ -342,13 +198,6 @@ void KCTriageView::initSymbolTable() auto symbolWidget = new QWidget; symbolWidget->setLayout(symbolLayout); - m_symbolTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); // Address - m_symbolTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); // Name - m_symbolTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); // Image - - m_symbolTable->setSelectionBehavior(QAbstractItemView::SelectRows); - m_symbolTable->setSelectionMode(QAbstractItemView::SingleSelection); - std::function<void(uint64_t)> navigateToAddress = [=](uint64_t addr) { ExecuteOnMainThread([addr, this](){ if (Settings::Instance()->Get<bool>("ui.view.graph.preferred")) diff --git a/view/kernelcache/ui/kctriage.h b/view/kernelcache/ui/kctriage.h index 07c24fad..7d8bdfc1 100644 --- a/view/kernelcache/ui/kctriage.h +++ b/view/kernelcache/ui/kctriage.h @@ -1,25 +1,26 @@ -#include <kernelcacheapi.h> -#include <binaryninjaapi.h> -#include "uitypes.h" -#include "viewframe.h" -#include "animation.h" -#include "uicontext.h" - -#include <QTableView> -#include <QStandardItemModel> -#include <QSortFilterProxyModel> #include <QHeaderView> -#include "filter.h" +#include <QSortFilterProxyModel> +#include <QStandardItemModel> #include <QStyledItemDelegate> - +#include <QTableView> +#include <binaryninjaapi.h> +#include <kernelcacheapi.h> +#include <progresstask.h> +#include "filter.h" #include "ui/fontsettings.h" +#include "uicontext.h" +#include "uitypes.h" +#include "viewframe.h" #ifndef BINARYNINJA_KCTRIAGE_H #define BINARYNINJA_KCTRIAGE_H + +using namespace KernelCacheAPI; + + class AddressColorDelegate : public QStyledItemDelegate { - public: explicit AddressColorDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {} @@ -28,6 +29,7 @@ public: QStyleOptionViewItem opt = option; initStyleOption(&opt, index); + opt.font = getMonospaceFont(qobject_cast<QWidget*>(parent())); opt.palette.setColor(QPalette::Text, getThemeColor(BNThemeColor::AddressColor)); opt.displayAlignment = Qt::AlignCenter | Qt::AlignVCenter; @@ -36,6 +38,21 @@ public: }; +class MonospaceFontDelegate : public QStyledItemDelegate { +public: + explicit MonospaceFontDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {} + + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override { + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + + opt.font = getMonospaceFont(qobject_cast<QWidget*>(parent())); + + QStyledItemDelegate::paint(painter, opt, index); + } +}; + + class FilterableTableView : public QTableView, public FilterTarget { Q_OBJECT @@ -73,7 +90,10 @@ public: void scrollToFirstItem() override { if (model()->rowCount() > 0) { - scrollTo(model()->index(0, 0)); + QModelIndex top = indexAt(rect().topLeft()); + if (top.isValid()) { + scrollTo(top); + } } } @@ -86,109 +106,177 @@ public: void selectFirstItem() override { if (model()->rowCount() > 0) { - QModelIndex firstIndex = model()->index(0, 0); - selectionModel()->select(firstIndex, QItemSelectionModel::ClearAndSelect); + QModelIndex top = indexAt(rect().topLeft()); + if (top.isValid()) { + selectionModel()->select(top, QItemSelectionModel::ClearAndSelect); + setCurrentIndex(top); + } } } void activateFirstItem() override { if (model()->rowCount() > 0) { - QModelIndex firstIndex = model()->index(0, 0); - setCurrentIndex(firstIndex); - emit activated(firstIndex); - } - } - - bool eventFilter(QObject* obj, QEvent* event) override { - if (event->type() == QEvent::KeyPress) { - auto keyEvent = dynamic_cast<QKeyEvent*>(event); - if (keyEvent->key() == Qt::Key_Escape) { - clearSelection(); - return true; - } - if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) { - emit activated(currentIndex()); - return true; + QModelIndex topLeft = indexAt(rect().topLeft()); + if (topLeft.isValid()) { + setCurrentIndex(topLeft); + emit activated(topLeft); } } - return QTableView::eventFilter(obj, event); } signals: void filterTextChanged(const QString& text); }; -class SymbolTableView; - -class SymbolTableModel : public QAbstractTableModel { - Q_OBJECT - SymbolTableView* m_parent; - QFont m_font; - std::string m_filter; - std::vector<KernelCacheAPI::KCSymbol> m_symbols; +class SymbolTableProxyModel : public QSortFilterProxyModel +{ +Q_OBJECT public: - explicit SymbolTableModel(SymbolTableView* parent); + SymbolTableProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent), m_timer(new QTimer(this)) + { + m_timer->setSingleShot(true); + connect(m_timer, &QTimer::timeout, this, &SymbolTableProxyModel::delayedFilterChanged); + } + + void setFilterString(const QString& filter) + { + QRegularExpression newRegEx(QRegularExpression::escape(filter), QRegularExpression::CaseInsensitiveOption); + if (m_filter != newRegEx) { + m_filter = std::move(newRegEx); + m_timer->start(200); + } + } - int rowCount(const QModelIndex& parent) const override; - int columnCount(const QModelIndex& parent) const override; - QVariant data(const QModelIndex& index, int role) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - void updateSymbols(); - void setFilter(std::string text); +protected: + bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override + { + if (m_filter.pattern().isEmpty()) + return true; - const KernelCacheAPI::KCSymbol& symbolAt(int row) const; + for (int column = 0; column < sourceModel()->columnCount(source_parent); ++column) + { + QModelIndex index = sourceModel()->index(source_row, column, source_parent); + QString data = sourceModel()->data(index).toString(); + if (m_filter.match(data).hasMatch()) + return true; + } + return false; + } + +private slots: + void delayedFilterChanged() + { + invalidateFilter(); + } + +private: + QRegularExpression m_filter; + QTimer* m_timer; }; class SymbolTableView : public QTableView, public FilterTarget { - Q_OBJECT +Q_OBJECT friend class SymbolTableModel; std::vector<KernelCacheAPI::KCSymbol> m_symbols; - - SymbolTableModel* m_model; + QStandardItemModel* m_model; + SymbolTableProxyModel* m_proxyModel; public: - SymbolTableView(QWidget* parent, Ref<KernelCacheAPI::KernelCache> cache); - ~SymbolTableView() override; + SymbolTableView(QWidget* parent, Ref<KernelCache>& cache) + : QTableView(parent), m_model(new QStandardItemModel(this)), m_proxyModel(new SymbolTableProxyModel(this)) + { + m_proxyModel->setSourceModel(m_model); + setModel(m_proxyModel); - void scrollToFirstItem() override { - if (model()->rowCount() > 0) { - scrollTo(model()->index(0, 0)); - } + // Set up the headers + m_model->setColumnCount(3); + m_model->setHorizontalHeaderLabels({"Address", "Name", "Image"}); + setFont(getMonospaceFont(parent)); + setItemDelegateForColumn(0, new AddressColorDelegate(this)); + setItemDelegateForColumn(1, new MonospaceFontDelegate(this)); + setItemDelegateForColumn(2, new MonospaceFontDelegate(this)); + + // Configure view settings + horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); + setEditTriggers(QAbstractItemView::NoEditTriggers); + setSelectionBehavior(QAbstractItemView::SelectRows); + setSelectionMode(QAbstractItemView::SingleSelection); + verticalHeader()->setVisible(false); + + setSortingEnabled(true); + + BackgroundThread::create(this)->thenBackground([this, cache](){ + m_symbols = cache->LoadAllSymbolsAndWait(); + })->thenMainThread([this](){ + updateSymbols(); + })->start(); } - void scrollToCurrentItem() override { - QModelIndex currentIndex = selectionModel()->currentIndex(); - if (currentIndex.isValid()) { - scrollTo(currentIndex); + ~SymbolTableView() override = default; + + void updateSymbols() + { + m_model->removeRows(0, m_model->rowCount()); + for (const auto& symbol : m_symbols) + { + QList<QStandardItem*> row; + row << new QStandardItem(QString("0x%1").arg(symbol.address, 0, 16)) + << new QStandardItem(QString::fromStdString(symbol.name)) + << new QStandardItem(QString::fromStdString(symbol.image)); + m_model->appendRow(row); } } - void selectFirstItem() override { - if (model()->rowCount() > 0) { - QModelIndex firstIndex = model()->index(0, 0); - selectionModel()->select(firstIndex, QItemSelectionModel::ClearAndSelect); - } + KernelCacheAPI::KCSymbol getSymbolAtRow(int row) const + { + QModelIndex proxyIndex = m_proxyModel->index(row, 0); + QModelIndex sourceIndex = m_proxyModel->mapToSource(proxyIndex); + return m_symbols[sourceIndex.row()]; } - void activateFirstItem() override { - if (model()->rowCount() > 0) { - QModelIndex firstIndex = model()->index(0, 0); - setCurrentIndex(firstIndex); - emit activated(firstIndex); + void scrollToFirstItem() override + { + scrollToTop(); + } + + void scrollToCurrentItem() override + { + scrollTo(selectionModel()->currentIndex()); + } + + void selectFirstItem() override + { + if (m_proxyModel->rowCount() > 0) { + QModelIndex idx = m_proxyModel->index(0, 0); + if (idx.isValid()) { + selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect); + setCurrentIndex(idx); + } } } - KernelCacheAPI::KCSymbol getSymbolAtRow(int row) const + void activateFirstItem() override { - return m_model->symbolAt(row); + if (m_proxyModel->rowCount() > 0) { + QModelIndex idx = m_proxyModel->index(0, 0); + if (idx.isValid()) { + setCurrentIndex(idx); + emit activated(idx); + } + } } - void setFilter(const std::string& filter) override; + void setFilter(const std::string& text) override + { + m_proxyModel->setFilterString(QString::fromStdString(text)); + } }; diff --git a/view/sharedcache/ui/dsctriage.cpp b/view/sharedcache/ui/dsctriage.cpp index e2cf8147..54fe4ed0 100644 --- a/view/sharedcache/ui/dsctriage.cpp +++ b/view/sharedcache/ui/dsctriage.cpp @@ -1,11 +1,10 @@ +#include <QHeaderView> +#include <QMessageBox> +#include <utility> #include "dsctriage.h" #include "globalarea.h" -#include "progresstask.h" #include "symboltable.h" #include "ui/fontsettings.h" -#include <QHeaderView> -#include <QMessageBox> -#include <utility> using namespace BinaryNinja; using namespace SharedCacheAPI; @@ -28,7 +27,6 @@ QWidget* DSCTriageViewType::create(BinaryViewRef data, ViewFrame* viewFrame) { if (data->GetTypeName() != VIEW_NAME) return nullptr; - // TODO: Check for dyld start. Then continue. return new DSCTriageView(viewFrame, data); } @@ -134,36 +132,36 @@ void DSCTriageView::loadImagesWithAddr(const std::vector<uint64_t>& addresses, b QWidget* DSCTriageView::initImageTable() { - auto loadImageTable = new FilterableTableView(this); + m_imageTable = new FilterableTableView(this); - m_imageModel = new QStandardItemModel(0, 3, loadImageTable); + m_imageModel = new QStandardItemModel(0, 3, m_imageTable); m_imageModel->setHorizontalHeaderLabels({"Address", "Loaded", "Name"}); // Apply custom column styling - loadImageTable->setItemDelegateForColumn(0, new AddressColorDelegate(loadImageTable)); - loadImageTable->setItemDelegateForColumn(1, new LoadedDelegate(loadImageTable)); + m_imageTable->setItemDelegateForColumn(0, new AddressColorDelegate(m_imageTable)); + m_imageTable->setItemDelegateForColumn(1, new LoadedDelegate(m_imageTable)); // Context menu - loadImageTable->setContextMenuPolicy(Qt::CustomContextMenu); - connect(loadImageTable, &QWidget::customContextMenuRequested, [this, loadImageTable](const QPoint &pos) { - QMenu contextMenu(tr("Load Image Actions"), loadImageTable); + m_imageTable->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_imageTable, &QWidget::customContextMenuRequested, [this](const QPoint &pos) { + QMenu contextMenu(tr("Load Image Actions"), m_imageTable); // Get number of selected images - auto selected = loadImageTable->selectionModel()->selectedRows(); + auto selected = m_imageTable->selectionModel()->selectedRows(); int selectedCount = 0; std::vector<uint64_t> addresses; for (const auto& idx : selected) { // Skip rows hidden by the filter - if (loadImageTable->isRowHidden(idx.row())) + if (m_imageTable->isRowHidden(idx.row())) continue; addresses.push_back(idx.data().toString().toULongLong(nullptr, 16)); selectedCount++; } - QAction noSelectionAction("No Images Selected", loadImageTable); - QAction loadImagesAction("", loadImageTable); - QAction loadImagesWithDepsAction("", loadImageTable); + QAction noSelectionAction("No Images Selected", m_imageTable); + QAction loadImagesAction("", m_imageTable); + QAction loadImagesWithDepsAction("", m_imageTable); if (selectedCount == 0) { noSelectionAction.setEnabled(false); @@ -188,20 +186,25 @@ QWidget* DSCTriageView::initImageTable() contextMenu.addAction(&loadImagesWithDepsAction); } - contextMenu.exec(loadImageTable->viewport()->mapToGlobal(pos)); + contextMenu.exec(m_imageTable->viewport()->mapToGlobal(pos)); }); auto loadImageButton = new QPushButton(); - connect(loadImageButton, &QPushButton::clicked, [this, loadImageTable](bool) { - auto selected = loadImageTable->selectionModel()->selectedRows(); + connect(loadImageButton, &QPushButton::clicked, [this](bool) { + // Collect only visible selected rows + QModelIndexList selected; + for (const auto& index : m_imageTable->selectionModel()->selectedRows()) { + if (!m_imageTable->isRowHidden(index.row())) { + selected.append(index); + } + } + + if (selected.empty()) + return; + std::vector<uint64_t> addresses; for (const auto& idx : selected) - { - // Skip rows hidden by the filter - if (loadImageTable->isRowHidden(idx.row())) - continue; addresses.push_back(idx.data().toString().toULongLong(nullptr, 16)); - } loadImagesWithAddr(addresses); }); loadImageButton->setText("Load Selected"); @@ -213,43 +216,43 @@ QWidget* DSCTriageView::initImageTable() refreshDataButton->setText("Refresh"); } // refreshDataButton - auto loadImageFilterEdit = new FilterEdit(loadImageTable); - connect(loadImageFilterEdit, &FilterEdit::textChanged, [loadImageTable](const QString& filter) { - loadImageTable->setFilter(filter.toStdString()); + auto loadImageFilterEdit = new FilterEdit(m_imageTable); + connect(loadImageFilterEdit, &FilterEdit::textChanged, [this](const QString& filter) { + m_imageTable->setFilter(filter.toStdString()); }); - connect(loadImageTable, &FilterableTableView::activated, this, [=](const QModelIndex& index) { + connect(m_imageTable, &FilterableTableView::activated, this, [=](const QModelIndex& index) { auto addr = m_imageModel->item(index.row(), 0)->text().toULongLong(nullptr, 16); loadImagesWithAddr({addr}); }); auto loadImageLayout = new QVBoxLayout; loadImageLayout->addWidget(loadImageFilterEdit); - loadImageLayout->addWidget(loadImageTable); + loadImageLayout->addWidget(m_imageTable); - auto buttonLayout = new QHBoxLayout; - buttonLayout->addWidget(loadImageButton); - buttonLayout->addWidget(refreshDataButton); - buttonLayout->setAlignment(Qt::AlignLeft); - loadImageLayout->addLayout(buttonLayout); + auto loadImageFooterLayout = new QHBoxLayout; + loadImageFooterLayout->addWidget(loadImageButton); + loadImageFooterLayout->addWidget(refreshDataButton); + loadImageFooterLayout->setAlignment(Qt::AlignLeft); + loadImageLayout->addLayout(loadImageFooterLayout); auto loadImageWidget = new QWidget; loadImageWidget->setLayout(loadImageLayout); - loadImageTable->setModel(m_imageModel); + m_imageTable->setModel(m_imageModel); - loadImageTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + m_imageTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - loadImageTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); - loadImageTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); - loadImageTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); + m_imageTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); + m_imageTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + m_imageTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); - loadImageTable->setSelectionBehavior(QAbstractItemView::SelectRows); - loadImageTable->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_imageTable->setSelectionBehavior(QAbstractItemView::SelectRows); + m_imageTable->setSelectionMode(QAbstractItemView::ExtendedSelection); - loadImageTable->setSortingEnabled(true); + m_imageTable->setSortingEnabled(true); - loadImageTable->verticalHeader()->setVisible(false); + m_imageTable->verticalHeader()->setVisible(false); m_triageTabs->addTab(loadImageWidget, "Images"); m_triageTabs->setCanCloseTab(loadImageWidget, false); @@ -262,51 +265,43 @@ void DSCTriageView::initSymbolTable() { m_symbolTable = new SymbolTableView(this); + // Apply custom column styling + m_symbolTable->setItemDelegateForColumn(0, new AddressColorDelegate(m_symbolTable)); + auto symbolFilterEdit = new FilterEdit(m_symbolTable); connect(symbolFilterEdit, &FilterEdit::textChanged, [this](const QString& filter) { m_symbolTable->setFilter(filter.toStdString()); }); - // Apply custom column styling - m_symbolTable->setItemDelegateForColumn(0, new AddressColorDelegate(m_symbolTable)); - auto loadSymbolImageButton = new QPushButton(); - { - connect(loadSymbolImageButton, &QPushButton::clicked, - [this](bool) { - auto selected = m_symbolTable->selectionModel()->selectedRows(); - std::vector<uint64_t> addresses; - for (const auto& row : selected) - addresses.push_back(row.data().toString().toULongLong(nullptr, 16)); - loadImagesWithAddr(addresses); - }); - loadSymbolImageButton->setText("Load Image"); - } // loadImageButton + connect(loadSymbolImageButton, &QPushButton::clicked, [this](bool) { + auto selected = m_symbolTable->selectionModel()->selectedRows(); + std::vector<uint64_t> addresses; + for (const auto& row : selected) + addresses.push_back(row.data().toString().toULongLong(nullptr, 16)); + loadImagesWithAddr(addresses); + }); + loadSymbolImageButton->setText("Load Image"); // Shows the current selected rows image name. - auto currentImageLabel = new QLabel(this); { - currentImageLabel->setText(""); - currentImageLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - } - - // Update the label whenever the selection changes. - connect(m_symbolTable->selectionModel(), &QItemSelectionModel::currentRowChanged, this, - [this, currentImageLabel](const QModelIndex ¤t, const QModelIndex &) { - auto symbol = m_symbolTable->getSymbolAtRow(current.row()); - auto controller = SharedCacheController::GetController(*this->m_data); - if (!controller) - return; - auto image = controller->GetImageContaining(symbol.address); - if (image) - currentImageLabel->setText("Image: " + QString::fromStdString(image->name)); - else - currentImageLabel->setText(""); - }); + auto currentImageLabel = new QLabel(this); + currentImageLabel->setText(""); + currentImageLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + connect(m_symbolTable->selectionModel(), &QItemSelectionModel::currentRowChanged, this, [this, currentImageLabel](const QModelIndex ¤t, const QModelIndex &) { + auto symbol = m_symbolTable->getSymbolAtRow(current.row()); + auto controller = SharedCacheController::GetController(*this->m_data); + if (!controller) + return; + auto image = controller->GetImageContaining(symbol.address); + if (image) + currentImageLabel->setText("Image: " + QString::fromStdString(image->name)); + else + currentImageLabel->setText(""); + }); auto symbolFooterLayout = new QHBoxLayout; symbolFooterLayout->addWidget(loadSymbolImageButton); symbolFooterLayout->addWidget(currentImageLabel); - symbolFooterLayout->setAlignment(Qt::AlignLeft); auto symbolLayout = new QVBoxLayout; diff --git a/view/sharedcache/ui/dsctriage.h b/view/sharedcache/ui/dsctriage.h index a16457e3..e847625e 100644 --- a/view/sharedcache/ui/dsctriage.h +++ b/view/sharedcache/ui/dsctriage.h @@ -1,24 +1,29 @@ -#include <binaryninjaapi.h> +#include <QHeaderView> #include <QItemDelegate> +#include <QPainter> +#include <QSortFilterProxyModel> +#include <QStandardItemModel> #include <QStyledItemDelegate> - -#include "uitypes.h" -#include "viewframe.h" -#include "animation.h" -#include "uicontext.h" - +#include <QTableView> +#include <binaryninjaapi.h> +#include <progresstask.h> +#include <sharedcacheapi.h> #include "filter.h" #include "symboltable.h" - #include "ui/fontsettings.h" +#include "uicontext.h" +#include "uitypes.h" +#include "viewframe.h" #ifndef BINARYNINJA_DSCTRIAGE_H #define BINARYNINJA_DSCTRIAGE_H +using namespace BinaryNinja; +using namespace SharedCacheAPI; + class AddressColorDelegate : public QStyledItemDelegate { - public: explicit AddressColorDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {} @@ -27,6 +32,7 @@ public: QStyleOptionViewItem opt = option; initStyleOption(&opt, index); + opt.font = getMonospaceFont(qobject_cast<QWidget*>(parent())); opt.palette.setColor(QPalette::Text, getThemeColor(BNThemeColor::AddressColor)); opt.displayAlignment = Qt::AlignCenter | Qt::AlignVCenter; @@ -130,7 +136,10 @@ public: void scrollToFirstItem() override { if (model()->rowCount() > 0) { - scrollTo(model()->index(0, 0)); + QModelIndex top = indexAt(rect().topLeft()); + if (top.isValid()) { + scrollTo(top); + } } } @@ -143,34 +152,39 @@ public: void selectFirstItem() override { if (model()->rowCount() > 0) { - QModelIndex firstIndex = model()->index(0, 0); - selectionModel()->select(firstIndex, QItemSelectionModel::ClearAndSelect); + QModelIndex top = indexAt(rect().topLeft()); + if (top.isValid()) { + selectionModel()->select(top, QItemSelectionModel::ClearAndSelect); + setCurrentIndex(top); + } } } void activateFirstItem() override { if (model()->rowCount() > 0) { - QModelIndex firstIndex = model()->index(0, 0); - setCurrentIndex(firstIndex); - emit activated(firstIndex); - } - } - - bool eventFilter(QObject* obj, QEvent* event) override { - if (event->type() == QEvent::KeyPress) { - auto* keyEvent = dynamic_cast<QKeyEvent*>(event); - if (keyEvent->key() == Qt::Key_Escape) { - clearSelection(); - return true; - } - if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) { - emit activated(currentIndex()); - return true; + QModelIndex topLeft = indexAt(rect().topLeft()); + if (topLeft.isValid()) { + setCurrentIndex(topLeft); + emit activated(topLeft); } } - return QTableView::eventFilter(obj, event); } +// bool eventFilter(QObject* obj, QEvent* event) override { +// if (event->type() == QEvent::KeyPress) { +// auto* keyEvent = dynamic_cast<QKeyEvent*>(event); +// if (keyEvent->key() == Qt::Key_Escape) { +// clearSelection(); +// return true; +// } +// if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) { +// emit activated(currentIndex()); +// return true; +// } +// } +// return QTableView::eventFilter(obj, event); +// } + signals: void filterTextChanged(const QString& text); }; @@ -184,6 +198,7 @@ class DSCTriageView : public QWidget, public View, public UIContextNotification SplitTabWidget* m_triageTabs; DockableTabCollection* m_triageCollection; + FilterableTableView* m_imageTable; QStandardItemModel* m_imageModel; SymbolTableView* m_symbolTable; diff --git a/view/sharedcache/ui/symboltable.cpp b/view/sharedcache/ui/symboltable.cpp index 1df7d529..b0247f2b 100644 --- a/view/sharedcache/ui/symboltable.cpp +++ b/view/sharedcache/ui/symboltable.cpp @@ -10,23 +10,27 @@ using namespace BinaryNinja; using namespace SharedCacheAPI; + SymbolTableModel::SymbolTableModel(SymbolTableView* parent) : QAbstractTableModel(parent), m_parent(parent) { // TODO: Need to implement updating this font if it is changed by the user m_font = getMonospaceFont(parent); } + int SymbolTableModel::rowCount(const QModelIndex& parent) const { Q_UNUSED(parent); return static_cast<int>(m_modelSymbols.size()); } + int SymbolTableModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); // We have 3 columns: Address, Type, Name return 3; } + QVariant SymbolTableModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || (role != Qt::DisplayRole && role != Qt::FontRole)) { return QVariant(); @@ -58,6 +62,7 @@ QVariant SymbolTableModel::data(const QModelIndex& index, int role) const { } } + QVariant SymbolTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole || orientation != Qt::Horizontal) { return QVariant(); @@ -75,12 +80,58 @@ QVariant SymbolTableModel::headerData(int section, Qt::Orientation orientation, } } + +void SymbolTableModel::sort(int column, Qt::SortOrder order) +{ + beginResetModel(); + + std::function<bool(const CacheSymbol&, const CacheSymbol&)> comparator; + + switch (column) + { + case 0: // Address column + comparator = [](const CacheSymbol& a, const CacheSymbol& b) { + return a.address < b.address; + }; + break; + case 1: // Type column + comparator = [](const CacheSymbol& a, const CacheSymbol& b) { + return GetSymbolTypeAsString(a.type) < GetSymbolTypeAsString(b.type); + }; + break; + case 2: // Name column + comparator = [](const CacheSymbol& a, const CacheSymbol& b) { + return a.name < b.name; + }; + break; + default: + endResetModel(); + return; + } + + if (order == Qt::DescendingOrder) + { + std::sort(m_modelSymbols.begin(), m_modelSymbols.end(), + [&comparator](const CacheSymbol& a, const CacheSymbol& b) { + return comparator(b, a); + }); + } + else + { + std::sort(m_modelSymbols.begin(), m_modelSymbols.end(), comparator); + } + + endResetModel(); +} + + void SymbolTableModel::updateSymbols(std::vector<CacheSymbol>&& symbols) { m_preparedSymbols = symbols; setFilter(m_filter); } + const CacheSymbol& SymbolTableModel::symbolAt(int row) const { return m_modelSymbols.at(row); diff --git a/view/sharedcache/ui/symboltable.h b/view/sharedcache/ui/symboltable.h index 2fc5c892..51ba88f0 100644 --- a/view/sharedcache/ui/symboltable.h +++ b/view/sharedcache/ui/symboltable.h @@ -2,12 +2,13 @@ #include <sharedcacheapi.h> #include "viewframe.h" -#include "animation.h" #include <QTableView> #include <QStandardItemModel> #include "filter.h" +#ifndef BINARYNINJA_DSCSYMBOLTABLE_H +#define BINARYNINJA_DSCSYMBOLTABLE_H class SymbolTableView; @@ -29,9 +30,11 @@ public: int columnCount(const QModelIndex& parent) const override; QVariant data(const QModelIndex& index, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + void sort(int column, Qt::SortOrder order) override; void updateSymbols(std::vector<SharedCacheAPI::CacheSymbol>&& symbols); void setFilter(std::string text); const SharedCacheAPI::CacheSymbol& symbolAt(int row) const; + }; @@ -44,45 +47,46 @@ Q_OBJECT public: explicit SymbolTableView(QWidget* parent); - ~SymbolTableView() override; + // Call this to populate the symbols from the given view. + void populateSymbols(BinaryNinja::BinaryView& view); + void scrollToFirstItem() override { - if (model()->rowCount() > 0) - { - scrollTo(model()->index(0, 0)); + if (model()->rowCount() > 0) { + QModelIndex top = indexAt(rect().topLeft()); + if (top.isValid()) + scrollTo(top); } } - // Call this to populate the symbols from the given view. - void populateSymbols(BinaryNinja::BinaryView& view); - void scrollToCurrentItem() override { QModelIndex currentIndex = selectionModel()->currentIndex(); if (currentIndex.isValid()) - { scrollTo(currentIndex); - } } void selectFirstItem() override { - if (model()->rowCount() > 0) - { - QModelIndex firstIndex = model()->index(0, 0); - selectionModel()->select(firstIndex, QItemSelectionModel::ClearAndSelect); + if (model()->rowCount() > 0) { + QModelIndex top = indexAt(rect().topLeft()); + if (top.isValid()) { + selectionModel()->select(top, QItemSelectionModel::ClearAndSelect); + setCurrentIndex(top); + } } } void activateFirstItem() override { - if (model()->rowCount() > 0) - { - QModelIndex firstIndex = model()->index(0, 0); - setCurrentIndex(firstIndex); - emit activated(firstIndex); + if (model()->rowCount() > 0) { + QModelIndex topLeft = indexAt(rect().topLeft()); + if (topLeft.isValid()) { + setCurrentIndex(topLeft); + emit activated(topLeft); + } } } @@ -92,4 +96,7 @@ public: } void setFilter(const std::string& filter) override; -};
\ No newline at end of file +}; + + +#endif // BINARYNINJA_DSCSYMBOLTABLE_H |
