diff options
| author | Alexander Taylor <alex@vector35.com> | 2025-04-09 12:47:00 -0400 |
|---|---|---|
| committer | Alexander Taylor <alex@vector35.com> | 2025-04-09 17:16:23 -0400 |
| commit | e3772366504d6d05b4809013cb4f4149ca64c4d4 (patch) | |
| tree | e3fcb0818bfede1a5f8fff0b1edd59422b517b67 | |
| parent | f78345462ebc25e0241d838f04104e5c6bfae863 (diff) | |
Shared cache and kernel cache UI cleanup.
One day, these will share more code, but today is not quite that day.
| -rw-r--r-- | view/kernelcache/ui/kctriage.cpp | 548 | ||||
| -rw-r--r-- | view/kernelcache/ui/kctriage.h | 79 | ||||
| -rw-r--r-- | view/sharedcache/ui/dsctriage.cpp | 621 | ||||
| -rw-r--r-- | view/sharedcache/ui/dsctriage.h | 128 | ||||
| -rw-r--r-- | view/sharedcache/ui/symboltable.cpp | 5 |
5 files changed, 645 insertions, 736 deletions
diff --git a/view/kernelcache/ui/kctriage.cpp b/view/kernelcache/ui/kctriage.cpp index 3fa70065..7b3cf9f0 100644 --- a/view/kernelcache/ui/kctriage.cpp +++ b/view/kernelcache/ui/kctriage.cpp @@ -1,59 +1,65 @@ -// -// Created by kat on 8/15/24. -// - +#include "globalarea.h" #include "kctriage.h" +#include "progresstask.h" #include "ui/fontsettings.h" +#include <QMessageBox> #include <QPainter> -#include <QTextBrowser> -#include "tabwidget.h" -#include "globalarea.h" -#include "progresstask.h" - #include <cmath> -#include <QMessageBox> - -#define QSETTINGS_KEY_SELECTED_TAB "KCTriage-SelectedTab" -#define QSETTINGS_KEY_TAB_LAYOUT "KCTriage-TabLayout" -#define QSETTINGS_KEY_IMAGELOAD_TAB_LAYOUT "KCTriage-ImageLoadTabLayout" -#define QSETTINGS_KEY_ALPHA_POPUP_SEEN "KCTriage-AlphaPopupSeenV2" +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) { + if (!index.isValid() || (role != Qt::DisplayRole && role != Qt::FontRole)) { return QVariant(); } - const KernelCacheAPI::KCSymbol& symbol = m_symbols.at(index.row()); + const KCSymbol& symbol = m_symbols.at(index.row()); - 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); + 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(); @@ -71,12 +77,14 @@ QVariant SymbolTableModel::headerData(int section, Qt::Orientation orientation, } } + void SymbolTableModel::updateSymbols() { m_symbols = m_parent->m_symbols; setFilter(m_filter); } -const KernelCacheAPI::KCSymbol& SymbolTableModel::symbolAt(int row) const { + +const KCSymbol& SymbolTableModel::symbolAt(int row) const { return m_symbols.at(row); } @@ -88,39 +96,38 @@ void SymbolTableModel::setFilter(std::string text) m_filter = text; m_symbols.clear(); - if (m_filter.empty()) - { - m_symbols = m_parent->m_symbols; - } - else + 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<KernelCacheAPI::KernelCache> cache) - : m_model(new SymbolTableModel(this)) { +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); - BackgroundThread::create(this)->thenBackground([this, cache=cache](){ + 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()); @@ -129,386 +136,267 @@ SymbolTableView::SymbolTableView(QWidget* parent, Ref<KernelCacheAPI::KernelCach })->start(); } + SymbolTableView::~SymbolTableView() { delete m_model; } + void SymbolTableView::setFilter(const std::string& filter) { m_model->setFilter(filter); } -KCTriageView::KCTriageView(QWidget* parent, BinaryViewRef data) : QWidget(parent), View(), m_data(data), m_cache(new KernelCacheAPI::KernelCache(data)) -{ - setBinaryDataNavigable(true); - setupView(this); - - m_triageCollection = new DockableTabCollection(); - m_triageTabs = new SplitTabWidget(m_triageCollection); - - auto triageTabStyle = new GlobalAreaTabStyle(); - m_triageTabs->setTabStyle(triageTabStyle); - - QSplitter* containerWidget = new QSplitter; - containerWidget->setOrientation(Qt::Vertical); - - QWidget* defaultWidget = nullptr; - - m_bottomRegionCollection = new DockableTabCollection(); - m_bottomRegionTabs = new SplitTabWidget(m_bottomRegionCollection); - m_bottomRegionTabs->setTabStyle(new GlobalAreaTabStyle()); +KCTriageViewType::KCTriageViewType() + : ViewType("KCTriage", "Kernel Cache Triage") +{} - auto loadImageTable = new FilterableTableView; - { - auto loadImageModel = new QStandardItemModel(0, 2, loadImageTable); - { - loadImageModel->setHorizontalHeaderLabels({"Name", "VM Address"}); - BackgroundThread::create(loadImageTable)->thenBackground([this](QVariant var) - { - QVariantList rows; - auto images = m_cache->GetImages(); +int KCTriageViewType::getPriority(BinaryViewRef data, const QString& filename) +{ + if (data->GetTypeName() == KC_VIEW_NAME) + return 100; + return 0; +} - auto newHeaders = std::make_shared<std::vector<KernelCacheAPI::KernelCacheMachOHeader>>(); - newHeaders->reserve(images.size()); - for (const auto& img : images) - { - if (auto header = m_cache->GetMachOHeaderForImage(img.name); header) - { - newHeaders->push_back(*header); - rows.push_back(QList<QVariant>{ - QString::fromStdString(img.name), - QString("0x%1").arg(header->textBase, 0, 16) - }); - } - } +QWidget* KCTriageViewType::create(BinaryViewRef data, ViewFrame* viewFrame) +{ + if (data->GetTypeName() != KC_VIEW_NAME) + return nullptr; + return new KCTriageView(viewFrame, data); +} - { - std::unique_lock<std::mutex> lock(m_headersMutex); - m_headers.swap(newHeaders); - } - return QVariant(rows); - })->thenMainThread([loadImageModel, loadImageTable](QVariant var){ - QVariantList rows = var.toList(); +void KCTriageViewType::Register() +{ + registerViewType(new KCTriageViewType()); +} - if (loadImageModel->rowCount() > 0) - { - loadImageModel->removeRows(0, loadImageModel->rowCount()); - } - for (const QVariant &rowVariant : rows) { - QVariantList row = rowVariant.toList(); +KCTriageView::KCTriageView(QWidget* parent, BinaryViewRef data) : QWidget(parent), View(), m_data(data), m_cache(new KernelCache(data)) +{ + setBinaryDataNavigable(true); + setupView(this); - QList<QStandardItem*> items; - for (const QVariant &cellValue : row) { - items.append(new QStandardItem(cellValue.toString())); - } + UIContext::registerNotification(this); - loadImageModel->appendRow(items); - loadImageTable->resizeColumnsToContents(); - } - })->start(); - } // loadImageModel + m_triageCollection = new DockableTabCollection(); + m_triageTabs = new SplitTabWidget(m_triageCollection); - auto loadImageButton = new CustomStyleFlatPushButton(); - { - connect(loadImageButton, &QPushButton::clicked, - [this, loadImageTable](bool) { - auto selected = loadImageTable->selectionModel()->selectedRows(); - if (selected.size() == 0) - { - return; - } + auto triageTabStyle = new GlobalAreaTabStyle(); + m_triageTabs->setTabStyle(triageTabStyle); - for (const auto& selection : selected) - { - auto name = selection.data().toString().toStdString(); - WorkerPriorityEnqueue([this, name]() { m_cache->LoadImageWithInstallName(name); }); - } - }); - loadImageButton->setText("Load"); + QWidget* defaultWidget = initImageTable(); + initSymbolTable(); - loadImageButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - loadImageButton->setMinimumWidth(100); - loadImageButton->setMinimumHeight(30); + m_layout = new QVBoxLayout(this); + m_layout->addWidget(m_triageTabs); + setLayout(m_layout); - } // loadImageButton - loadImageTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + // XXX: RefreshData - auto loadImageFilterEdit = new FilterEdit(loadImageTable); - { - connect(loadImageFilterEdit, &FilterEdit::textChanged, [loadImageTable](const QString& filter) { - loadImageTable->setFilter(filter.toStdString()); - }); - } // loadImageFilterEdit + m_triageTabs->selectWidget(defaultWidget); +} - connect(loadImageTable, &FilterableTableView::activated, this, [=](const QModelIndex& index) - { - auto selected = loadImageTable->selectionModel()->selectedRows(); - if (selected.size() == 0) - { - return; - } - for (const auto& selection : selected) - { - auto name = selection.data().toString().toStdString(); - WorkerPriorityEnqueue([this, name]() { m_cache->LoadImageWithInstallName(name); }); - } - }); +KCTriageView::~KCTriageView() +{ + UIContext::unregisterNotification(this); +} - auto loadImageLayout = new QVBoxLayout; - loadImageLayout->addWidget(loadImageFilterEdit); - loadImageLayout->addWidget(loadImageTable); - loadImageLayout->addWidget(loadImageButton); - auto loadImageWidget = new QWidget; - loadImageWidget->setLayout(loadImageLayout); +QWidget* KCTriageView::initImageTable() +{ + m_imageTable = new FilterableTableView(this); - m_bottomRegionTabs->addTab(loadImageWidget, "Load an Image"); + m_imageModel = new QStandardItemModel(0, 2, m_imageTable); + m_imageModel->setHorizontalHeaderLabels({"VM Address", "Name"}); - loadImageTable->setModel(loadImageModel); + // Apply custom column styling + m_imageTable->setItemDelegateForColumn(0, new AddressColorDelegate(m_imageTable)); - loadImageTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); - loadImageTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + BackgroundThread::create(m_imageTable)->thenBackground([this](const QVariant var) { + QVariantList rows; - loadImageTable->setSelectionBehavior(QAbstractItemView::SelectRows); + auto images = m_cache->GetImages(); - m_triageTabs->addTab(loadImageWidget, "Images"); - defaultWidget = loadImageWidget; - m_triageTabs->setCanCloseTab(loadImageWidget, false); - } // loadImageTable + auto newHeaders = std::make_shared<std::vector<KernelCacheMachOHeader>>(); + newHeaders->reserve(images.size()); - auto symbolSearch = new SymbolTableView(this, m_cache); - { - auto symbolFilterEdit = new FilterEdit(symbolSearch); + for (const auto& img : images) { - connect(symbolFilterEdit, &FilterEdit::textChanged, [symbolSearch](const QString& filter) { - symbolSearch->setFilter(filter.toStdString()); - }); - } - - auto symbolLayout = new QVBoxLayout; - symbolLayout->addWidget(symbolFilterEdit); - symbolLayout->addWidget(symbolSearch); - - auto symbolWidget = new QWidget; - symbolWidget->setLayout(symbolLayout); - - symbolSearch->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); // Address - symbolSearch->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); // Name - symbolSearch->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); // Image - - symbolSearch->setSelectionBehavior(QAbstractItemView::SelectRows); - symbolSearch->setSelectionMode(QAbstractItemView::SingleSelection); - - std::function<void(uint64_t)> navigateToAddress = [=](uint64_t addr){ - ExecuteOnMainThread([addr, this](){ - if (BinaryNinja::Settings::Instance()->Get<bool>("ui.view.graph.preferred")) - m_data->Navigate("Graph:KCView", addr); - else - m_data->Navigate("Linear:KCView", addr); - }); - }; - - connect(symbolSearch, &SymbolTableView::activated, this, [=](const QModelIndex& index) + if (auto header = m_cache->GetMachOHeaderForImage(img.name); header) { - auto symbol = symbolSearch->getSymbolAtRow(index.row()); - WorkerPriorityEnqueue([this, symbol, navigateToAddress]() - { - if (m_data->IsValidOffset(symbol.address)) - navigateToAddress(symbol.address); - else - { - m_cache->LoadImageWithInstallName(symbol.image); - navigateToAddress(symbol.address); - } - }); - }); - - m_triageTabs->addTab(symbolWidget, "Symbol Search"); - m_triageTabs->setCanCloseTab(symbolWidget, false); - } // symbolSearch - - { // Doc tabs - - QTextBrowser *mainDocBrowser = new QTextBrowser(this); - { - mainDocBrowser->setOpenExternalLinks(true); - auto alphaHtml = - R"( -<h1>KernelCache Parser</h1> - -<p> This parser has support for MH_FILESET (>= macOS 11, >= iOS 14) kernels. </p> - -<h2> Getting the latest version of the plugin </h2> -<p> We frequently release "dev" builds which will contain the latest version of the KernelCache plugin (and many other things). </p> -<p> You can find instructions on how to install these builds <a href="https://docs.binary.ninja/guide/index.html#development-branch">here</a>. </p> - -<h3> Reading / building the source </h3> -<p> Like most of our platforms, architectures, debug information parsing, and the entire API and documentation, this plugin is open source. </p> -<p> You can read the source and find instructions for building it <a href="https://github.com/Vector35/binaryninja-api/tree/dev/view/kernelcache">here</a>. </p> -<p> Contributions are always welcome! </p> -)"; - mainDocBrowser->setHtml(alphaHtml); - - m_triageTabs->addTab(mainDocBrowser, "KernelCache Parser"); - m_triageTabs->setCanCloseTab(mainDocBrowser, false); - + newHeaders->push_back(*header); + rows.push_back(QList<QVariant>{ + QString("0x%1").arg(header->textBase, 0, 16), + QString::fromStdString(img.name) + }); + } } - } - containerWidget->addWidget(m_bottomRegionTabs); + std::unique_lock<std::mutex> lock(m_headersMutex); + m_headers.swap(newHeaders); - m_layout = new QVBoxLayout(this); - m_layout->addWidget(m_triageTabs); - setLayout(m_layout); + return QVariant(rows); + })->thenMainThread([this](const QVariant var) { + QVariantList rows = var.toList(); - m_triageTabs->selectWidget(defaultWidget); -} + if (m_imageModel->rowCount() > 0) + m_imageModel->removeRows(0, m_imageModel->rowCount()); + for (const QVariant &rowVariant : rows) { + QVariantList row = rowVariant.toList(); -QFont KCTriageView::getFont() -{ - return getMonospaceFont(this); -} + QList<QStandardItem*> items; + for (const QVariant &cellValue : row) + items.append(new QStandardItem(cellValue.toString())); + m_imageModel->appendRow(items); + m_imageTable->resizeColumnsToContents(); + } + })->start(); -BinaryViewRef KCTriageView::getData() -{ - return m_data; -} + auto loadImageButton = new QPushButton(); + connect(loadImageButton, &QPushButton::clicked, [this](bool) { + // Collect only visible selected rows + QModelIndexList visibleSelectedRows; + for (const auto& index : m_imageTable->selectionModel()->selectedRows()) { + if (!m_imageTable->isRowHidden(index.row())) { + visibleSelectedRows.append(index); + } + } + if (visibleSelectedRows.empty()) + return; -bool KCTriageView::navigate(uint64_t offset) -{ - return false; -} + for (const auto& selection : visibleSelectedRows) { + auto name = selection.data().toString().toStdString(); + WorkerPriorityEnqueue([this, name]() { m_cache->LoadImageWithInstallName(name); }); + } + }); + loadImageButton->setText("Load Selected"); + auto loadImageFilterEdit = new FilterEdit(m_imageTable); + connect(loadImageFilterEdit, &FilterEdit::textChanged, [this](const QString& filter) { + m_imageTable->setFilter(filter.toStdString()); + }); -uint64_t KCTriageView::getCurrentOffset() -{ - return 0; -} + connect(m_imageTable, &FilterableTableView::activated, this, [=](const QModelIndex& index) { + auto selected = m_imageModel->item(index.row(), 0); + auto name = selected->text().toStdString(); + WorkerPriorityEnqueue([this, name]() { m_cache->LoadImageWithInstallName(name); }); + }); + auto loadImageLayout = new QVBoxLayout; + loadImageLayout->addWidget(loadImageFilterEdit); + loadImageLayout->addWidget(m_imageTable); + loadImageLayout->addWidget(loadImageButton); -CollapsibleSection::CollapsibleSection(QWidget* parent) - : QWidget(parent) -{ - auto layout = new QVBoxLayout(this); - { - layout->setContentsMargins(0, 0, 0, 0); + auto buttonLayout = new QHBoxLayout; + buttonLayout->addWidget(loadImageButton); + buttonLayout->setAlignment(Qt::AlignLeft); + loadImageLayout->addLayout(buttonLayout); - auto hLayout = new QHBoxLayout; - { - hLayout->setContentsMargins(0, 0, 0, 0); + auto loadImageWidget = new QWidget; + loadImageWidget->setLayout(loadImageLayout); - m_titleLabel = new QLabel; - m_titleLabel->setStyleSheet("font-weight: bold; font-size: 16px;"); - hLayout->addWidget(m_titleLabel, 1); + m_imageTable->setModel(m_imageModel); - m_subtitleRightLabel = new QLabel; - m_subtitleRightLabel->setStyleSheet("font-size: 12px;"); - hLayout->addWidget(m_subtitleRightLabel); + m_imageTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - m_collapseButton = new CustomStyleFlatPushButton; - m_collapseButton->setFlat(true); - m_collapseButton->setCheckable(true); - } + m_imageTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); + m_imageTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); - layout->addLayout(hLayout); - } + m_imageTable->setSelectionBehavior(QAbstractItemView::SelectRows); + m_imageTable->setSelectionMode(QAbstractItemView::ExtendedSelection); - m_contentWidgetContainer = new QWidget; - { - layout->addWidget(m_contentWidgetContainer); - new QVBoxLayout(m_contentWidgetContainer); - } + m_imageTable->setSortingEnabled(true); -} + m_imageTable->verticalHeader()->setVisible(false); + m_triageTabs->addTab(loadImageWidget, "Images"); + m_triageTabs->setCanCloseTab(loadImageWidget, false); -void CollapsibleSection::setTitle(const QString& title) -{ - m_titleLabel->setText(title); + return loadImageWidget; // For use as the default widget } -void CollapsibleSection::setSubtitleRight(const QString& subtitle) +void KCTriageView::initSymbolTable() { - m_subtitleRightLabel->setVisible(subtitle != ""); - m_subtitleRightLabel->setText(subtitle); -} + m_symbolTable = new SymbolTableView(this, m_cache); + auto symbolFilterEdit = new FilterEdit(m_symbolTable); + connect(symbolFilterEdit, &FilterEdit::textChanged, [this](const QString& filter) { + m_symbolTable->setFilter(filter.toStdString()); + }); -void CollapsibleSection::setContentWidget(QWidget* contentWidget) -{ - m_contentWidget = contentWidget; - m_contentWidgetContainer->layout()->addWidget(contentWidget); -} + // Apply custom column styling + m_symbolTable->setItemDelegateForColumn(0, new AddressColorDelegate(m_symbolTable)); + auto symbolLayout = new QVBoxLayout; + symbolLayout->addWidget(symbolFilterEdit); + symbolLayout->addWidget(m_symbolTable); -QSize CollapsibleSection::sizeHint() const -{ - return QWidget::sizeHint(); -} + 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 -void CollapsibleSection::setCollapsed(bool collapsed, bool animated) -{ - if (collapsed == m_collapsed) - { - return; - } + m_symbolTable->setSelectionBehavior(QAbstractItemView::SelectRows); + m_symbolTable->setSelectionMode(QAbstractItemView::SingleSelection); - m_collapsed = collapsed; + std::function<void(uint64_t)> navigateToAddress = [=](uint64_t addr) { + ExecuteOnMainThread([addr, this](){ + if (Settings::Instance()->Get<bool>("ui.view.graph.preferred")) + m_data->Navigate("Graph:KCView", addr); + else + m_data->Navigate("Linear:KCView", addr); + }); + }; - if (m_collapsed) - { - m_contentWidget->hide(); - } - else + connect(m_symbolTable, &SymbolTableView::activated, this, [=](const QModelIndex& index) { - m_contentWidget->show(); - } + auto symbol = m_symbolTable->getSymbolAtRow(index.row()); + WorkerPriorityEnqueue([this, symbol, navigateToAddress]() { + if (m_data->IsValidOffset(symbol.address)) + navigateToAddress(symbol.address); + else + { + m_cache->LoadImageWithInstallName(symbol.image); + navigateToAddress(symbol.address); + } + }); + }); - if (animated) - { - m_onContentAddedAnimation->start(); - } + m_triageTabs->addTab(symbolWidget, "Symbols"); + m_triageTabs->setCanCloseTab(symbolWidget, false); } -KCTriageViewType::KCTriageViewType() - : ViewType("KCTriage", "Kernel Cache Triage") +QFont KCTriageView::getFont() { - + return getMonospaceFont(this); } -int KCTriageViewType::getPriority(BinaryViewRef data, const QString& filename) +BinaryViewRef KCTriageView::getData() { - if (data->GetTypeName() == KC_VIEW_NAME) - { - return 100; - } - return 0; + return m_data; } -QWidget* KCTriageViewType::create(BinaryViewRef data, ViewFrame* viewFrame) +bool KCTriageView::navigate(uint64_t offset) { - if (data->GetTypeName() != KC_VIEW_NAME) - { - return nullptr; - } - return new KCTriageView(viewFrame, data); + // TODO: We have to set this to true otherwise view restore does not pickup this view. + return true; } -void KCTriageViewType::Register() +uint64_t KCTriageView::getCurrentOffset() { - ViewType::registerViewType(new KCTriageViewType()); + return 0; } diff --git a/view/kernelcache/ui/kctriage.h b/view/kernelcache/ui/kctriage.h index a6b7f7ac..07c24fad 100644 --- a/view/kernelcache/ui/kctriage.h +++ b/view/kernelcache/ui/kctriage.h @@ -1,7 +1,3 @@ -// -// Created by kat on 8/15/24. -// - #include <kernelcacheapi.h> #include <binaryninjaapi.h> #include "uitypes.h" @@ -14,37 +10,29 @@ #include <QSortFilterProxyModel> #include <QHeaderView> #include "filter.h" +#include <QStyledItemDelegate> + +#include "ui/fontsettings.h" #ifndef BINARYNINJA_KCTRIAGE_H #define BINARYNINJA_KCTRIAGE_H -class CollapsibleSection : public QWidget +class AddressColorDelegate : public QStyledItemDelegate { - Q_OBJECT - - QLabel* m_titleLabel; - QLabel* m_subtitleRightLabel; - QPushButton* m_collapseButton; - - bool m_collapsed = true; - - Animation* m_onContentAddedAnimation; - - QWidget* m_contentWidgetContainer; - QWidget* m_contentWidget; - -protected: - QSize sizeHint() const override; public: - CollapsibleSection(QWidget* parent); - void setTitle(const QString& title); - void setSubtitleRight(const QString& subtitle); + explicit AddressColorDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {} - void setContentWidget(QWidget* contentWidget); + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override + { + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); - void setCollapsed(bool collapsed, bool animated = true); - bool isCollapsed() const { return m_collapsed; } + opt.palette.setColor(QPalette::Text, getThemeColor(BNThemeColor::AddressColor)); + opt.displayAlignment = Qt::AlignCenter | Qt::AlignVCenter; + + QStyledItemDelegate::paint(painter, opt, index); + } }; @@ -54,12 +42,13 @@ class FilterableTableView : public QTableView, public FilterTarget { bool m_filterByHiding; public: - FilterableTableView(QWidget* parent = nullptr, bool filterByHiding = true) + explicit FilterableTableView(QWidget* parent = nullptr, bool filterByHiding = true) : QTableView(parent), m_filterByHiding(filterByHiding) { viewport()->installEventFilter(this); + setFont(getMonospaceFont(parent)); } - ~FilterableTableView() override {} + ~FilterableTableView() override = default; void setFilter(const std::string& filter) override { if (!m_filterByHiding) @@ -112,7 +101,7 @@ public: bool eventFilter(QObject* obj, QEvent* event) override { if (event->type() == QEvent::KeyPress) { - QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); + auto keyEvent = dynamic_cast<QKeyEvent*>(event); if (keyEvent->key() == Qt::Key_Escape) { clearSelection(); return true; @@ -135,19 +124,18 @@ class SymbolTableModel : public QAbstractTableModel { Q_OBJECT SymbolTableView* m_parent; + QFont m_font; std::string m_filter; std::vector<KernelCacheAPI::KCSymbol> m_symbols; public: explicit SymbolTableModel(SymbolTableView* parent); - int rowCount(const QModelIndex& parent = QModelIndex()) const override; - int columnCount(const QModelIndex& parent = QModelIndex()) const override; - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - + 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); const KernelCacheAPI::KCSymbol& symbolAt(int row) const; @@ -165,7 +153,7 @@ class SymbolTableView : public QTableView, public FilterTarget public: SymbolTableView(QWidget* parent, Ref<KernelCacheAPI::KernelCache> cache); - virtual ~SymbolTableView() override; + ~SymbolTableView() override; void scrollToFirstItem() override { if (model()->rowCount() > 0) { @@ -204,29 +192,36 @@ public: }; -class KCTriageView : public QWidget, public View +class KCTriageView : public QWidget, public View, public UIContextNotification { BinaryViewRef m_data; QVBoxLayout* m_layout; - Ref<KernelCacheAPI::KernelCache> m_cache; - std::mutex m_headersMutex; - std::shared_ptr<std::vector<KernelCacheAPI::KernelCacheMachOHeader>> m_headers; + Ref<KernelCacheAPI::KernelCache> m_cache; SplitTabWidget* m_triageTabs; DockableTabCollection* m_triageCollection; - SplitTabWidget* m_bottomRegionTabs; - DockableTabCollection* m_bottomRegionCollection; + FilterableTableView* m_imageTable; + QStandardItemModel* m_imageModel; + SymbolTableView* m_symbolTable; + + std::mutex m_headersMutex; + std::shared_ptr<std::vector<KernelCacheAPI::KernelCacheMachOHeader>> m_headers; public: KCTriageView(QWidget* parent, BinaryViewRef data); + ~KCTriageView() override; BinaryViewRef getData() override; void setSelectionOffsets(BNAddressRange range) override {}; QFont getFont() override; bool navigate(uint64_t offset) override; uint64_t getCurrentOffset() override; + +private: + QWidget* initImageTable(); + void initSymbolTable(); }; diff --git a/view/sharedcache/ui/dsctriage.cpp b/view/sharedcache/ui/dsctriage.cpp index 09b075f0..6b0ec0d5 100644 --- a/view/sharedcache/ui/dsctriage.cpp +++ b/view/sharedcache/ui/dsctriage.cpp @@ -1,23 +1,21 @@ -// -// Created by kat on 8/15/24. -// - -#include "ui/fontsettings.h" -#include "tabwidget.h" +#include "dsctriage.h" #include "globalarea.h" #include "progresstask.h" -#include <QMessageBox> -#include <QHeaderView> -#include "dsctriage.h" #include "symboltable.h" +#include "ui/fontsettings.h" +#include <QHeaderView> +#include <QMessageBox> +#include <utility> using namespace BinaryNinja; using namespace SharedCacheAPI; + DSCTriageViewType::DSCTriageViewType() : ViewType("DSCTriage", "Dyld Shared Cache Triage") {} + int DSCTriageViewType::getPriority(BinaryViewRef data, const QString& filename) { if (data->GetTypeName() == VIEW_NAME) @@ -25,6 +23,7 @@ int DSCTriageViewType::getPriority(BinaryViewRef data, const QString& filename) return 0; } + QWidget* DSCTriageViewType::create(BinaryViewRef data, ViewFrame* viewFrame) { if (data->GetTypeName() != VIEW_NAME) @@ -33,12 +32,14 @@ QWidget* DSCTriageViewType::create(BinaryViewRef data, ViewFrame* viewFrame) return new DSCTriageView(viewFrame, data); } + void DSCTriageViewType::Register() { registerViewType(new DSCTriageViewType()); } -DSCTriageView::DSCTriageView(QWidget* parent, BinaryViewRef data) : QWidget(parent), m_data(data) + +DSCTriageView::DSCTriageView(QWidget* parent, BinaryViewRef data) : QWidget(parent), m_data(std::move(data)) { setBinaryDataNavigable(false); setupView(this); @@ -51,408 +52,423 @@ DSCTriageView::DSCTriageView(QWidget* parent, BinaryViewRef data) : QWidget(pare auto triageTabStyle = new GlobalAreaTabStyle(); m_triageTabs->setTabStyle(triageTabStyle); - QWidget* defaultWidget = nullptr; + QWidget* defaultWidget = initImageTable(); + initSymbolTable(); + initCacheInfoTables(); - auto loadImagesWithAddr = [this](const std::vector<uint64_t>& addresses, bool includeDependencies = false) { - auto controller = SharedCacheController::GetController(*this->m_data); - if (!controller) - return; + m_layout = new QVBoxLayout(this); + m_layout->addWidget(m_triageTabs); + setLayout(m_layout); + + // In case we have already initialized the controller (user has opened this view type again) + // we will call refresh data. If this is the first triage view constructed (i.e. before view init) then this + // will do nothing. + RefreshData(); + + m_triageTabs->selectWidget(defaultWidget); +} + + +DSCTriageView::~DSCTriageView() +{ + UIContext::unregisterNotification(this); +} + + +void DSCTriageView::loadImagesWithAddr(const std::vector<uint64_t>& addresses, bool includeDependencies) { + auto controller = SharedCacheController::GetController(*this->m_data); + if (!controller) + return; - std::map<uint64_t, CacheImage> images = {}; - for (const uint64_t& addr : addresses) + std::map<uint64_t, CacheImage> images = {}; + for (const uint64_t& addr : addresses) + { + auto image = controller->GetImageContaining(addr); + // Only try to load if we have not already. + if (image.has_value() && !controller->IsImageLoaded(*image)) { - auto image = controller->GetImageContaining(addr); - // Only try to load if we have not already. - if (image.has_value() && !controller->IsImageLoaded(*image)) - { - images.insert({image->headerAddress, *image}); + images.insert({image->headerAddress, *image}); - // TODO: We currently only add direct dependencies, may want to make the depth configurable? - if (includeDependencies) + // TODO: We currently only add direct dependencies, may want to make the depth configurable? + if (includeDependencies) + { + auto dependencies = controller->GetImageDependencies(*image); + for (const auto& depName : dependencies) { - auto dependencies = controller->GetImageDependencies(*image); - for (const auto& depName : dependencies) + auto depImage = controller->GetImageWithName(depName); + if (depImage.has_value() && !controller->IsImageLoaded(*depImage)) { - auto depImage = controller->GetImageWithName(depName); - if (depImage.has_value() && !controller->IsImageLoaded(*depImage)) - { - images.insert({depImage->headerAddress, *depImage}); - } + images.insert({depImage->headerAddress, *depImage}); } } } - } + } - // Don't create a worker action if we don't have any images. - if (images.empty()) - return; + // Don't create a worker action if we don't have any images. + if (images.empty()) + return; - WorkerPriorityEnqueue([controller, this, images]() { - size_t loadedImages = 0; - const std::string initialLoad = fmt::format("Loading images... (0/{})", images.size()); - auto imageLoadTask = BackgroundTask(initialLoad, true); + WorkerPriorityEnqueue([controller, this, images]() { + size_t loadedImages = 0; + const std::string initialLoad = fmt::format("Loading images... (0/{})", images.size()); + auto imageLoadTask = BackgroundTask(initialLoad, true); - for (const auto& [addr, image] : images) - { - if (imageLoadTask.IsCancelled()) - break; - std::string newLoad = fmt::format("Loading images... ({}/{})", loadedImages++, images.size()); - imageLoadTask.SetProgressText(newLoad); - if (controller->ApplyImage(*this->m_data, image)) - setImageLoaded(image.headerAddress); - } - imageLoadTask.Finish(); + for (const auto& [addr, image] : images) + { + if (imageLoadTask.IsCancelled()) + break; + std::string newLoad = fmt::format("Loading images... ({}/{})", loadedImages++, images.size()); + imageLoadTask.SetProgressText(newLoad); + if (controller->ApplyImage(*this->m_data, image)) + setImageLoaded(image.headerAddress); + } + imageLoadTask.Finish(); + + // We have loaded images, lets make sure to update analysis! + this->m_data->AddAnalysisOption("linearsweep"); + this->m_data->UpdateAnalysis(); + }); +} - // We have loaded images, lets make sure to update analysis! - this->m_data->AddAnalysisOption("linearsweep"); - this->m_data->UpdateAnalysis(); - }); - }; - // Tab: Images +QWidget* DSCTriageView::initImageTable() +{ auto loadImageTable = new FilterableTableView(this); - { - m_imageModel = new QStandardItemModel(0, 3, loadImageTable); - m_imageModel->setHorizontalHeaderLabels({"Address", "Loaded", "Name"}); - // Apply custom column styling - loadImageTable->setItemDelegateForColumn(0, new AddressColorDelegate(loadImageTable)); - loadImageTable->setItemDelegateForColumn(1, new LoadedDelegate(loadImageTable)); + m_imageModel = new QStandardItemModel(0, 3, loadImageTable); + m_imageModel->setHorizontalHeaderLabels({"Address", "Loaded", "Name"}); - // Context menu - loadImageTable->setContextMenuPolicy(Qt::CustomContextMenu); - connect(loadImageTable, &QWidget::customContextMenuRequested, [loadImageTable, loadImagesWithAddr](const QPoint &pos) { - QMenu contextMenu(tr("Load Image Actions"), loadImageTable); + // Apply custom column styling + loadImageTable->setItemDelegateForColumn(0, new AddressColorDelegate(loadImageTable)); + loadImageTable->setItemDelegateForColumn(1, new LoadedDelegate(loadImageTable)); - // Get number of selected images - auto selected = loadImageTable->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())) - continue; - addresses.push_back(idx.data().toString().toULongLong(nullptr, 16)); - selectedCount++; - } + // Context menu + loadImageTable->setContextMenuPolicy(Qt::CustomContextMenu); + connect(loadImageTable, &QWidget::customContextMenuRequested, [this, loadImageTable](const QPoint &pos) { + QMenu contextMenu(tr("Load Image Actions"), loadImageTable); - QAction noSelectionAction("No Images Selected", loadImageTable); - QAction loadImagesAction("", loadImageTable); - QAction loadImagesWithDepsAction("", loadImageTable); - if (selectedCount == 0) - { - noSelectionAction.setEnabled(false); - contextMenu.addAction(&noSelectionAction); - } - else - { - // Format action text for loading selected images - QString loadActionText = (selectedCount == 1) ? "Load Selected Image" : QString("Load %1 Selected Images").arg(selectedCount); - loadImagesAction.setText(loadActionText); - connect(&loadImagesAction, &QAction::triggered, [loadImagesWithAddr, addresses]() { - loadImagesWithAddr(addresses, false); - }); - contextMenu.addAction(&loadImagesAction); - - // Format action text for loading selected images with dependencies - QString loadWithDepsActionText = (selectedCount == 1) ? "Load Selected Image and Dependencies" : QString("Load %1 Selected Images and Dependencies").arg(selectedCount); - loadImagesWithDepsAction.setText(loadWithDepsActionText); - connect(&loadImagesWithDepsAction, &QAction::triggered, [loadImagesWithAddr, addresses]() { - loadImagesWithAddr(addresses, true); - }); - contextMenu.addAction(&loadImagesWithDepsAction); - } + // Get number of selected images + auto selected = loadImageTable->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())) + continue; + addresses.push_back(idx.data().toString().toULongLong(nullptr, 16)); + selectedCount++; + } - contextMenu.exec(loadImageTable->viewport()->mapToGlobal(pos)); - }); + QAction noSelectionAction("No Images Selected", loadImageTable); + QAction loadImagesAction("", loadImageTable); + QAction loadImagesWithDepsAction("", loadImageTable); + if (selectedCount == 0) + { + noSelectionAction.setEnabled(false); + contextMenu.addAction(&noSelectionAction); + } + else + { + // Format action text for loading selected images + QString loadActionText = (selectedCount == 1) ? "Load Selected Image" : QString("Load %1 Selected Images").arg(selectedCount); + loadImagesAction.setText(loadActionText); + connect(&loadImagesAction, &QAction::triggered, [this, addresses]() { + loadImagesWithAddr(addresses, false); + }); + contextMenu.addAction(&loadImagesAction); - auto loadImageButton = new QPushButton(); - connect(loadImageButton, &QPushButton::clicked, - [loadImageTable, loadImagesWithAddr](bool) { - auto selected = loadImageTable->selectionModel()->selectedRows(); - 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); + // Format action text for loading selected images with dependencies + QString loadWithDepsActionText = (selectedCount == 1) ? "Load Selected Image and Dependencies" : QString("Load %1 Selected Images and Dependencies").arg(selectedCount); + loadImagesWithDepsAction.setText(loadWithDepsActionText); + connect(&loadImagesWithDepsAction, &QAction::triggered, [this, addresses]() { + this->loadImagesWithAddr(addresses, true); }); - loadImageButton->setText("Load Selected"); + contextMenu.addAction(&loadImagesWithDepsAction); + } + + contextMenu.exec(loadImageTable->viewport()->mapToGlobal(pos)); + }); - auto refreshDataButton = new QPushButton(); + auto loadImageButton = new QPushButton(); + connect(loadImageButton, &QPushButton::clicked, [this, loadImageTable](bool) { + auto selected = loadImageTable->selectionModel()->selectedRows(); + std::vector<uint64_t> addresses; + for (const auto& idx : selected) { - // TODO: Might want to introduce a cooldown for this button (if we even keep it) - connect(refreshDataButton, &QPushButton::clicked, [this](bool) { RefreshData(); }); - refreshDataButton->setText("Refresh"); - } // refreshDataButton + // 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"); - auto loadImageFilterEdit = new FilterEdit(loadImageTable); - connect(loadImageFilterEdit, &FilterEdit::textChanged, [loadImageTable](const QString& filter) { - loadImageTable->setFilter(filter.toStdString()); - }); + auto refreshDataButton = new QPushButton(); + { + // TODO: Might want to introduce a cooldown for this button (if we even keep it) + connect(refreshDataButton, &QPushButton::clicked, [this](bool) { RefreshData(); }); + refreshDataButton->setText("Refresh"); + } // refreshDataButton - connect(loadImageTable, &FilterableTableView::activated, this, [=](const QModelIndex& index) { - auto addr = m_imageModel->item(index.row(), 0)->text().toULongLong(nullptr, 16); - loadImagesWithAddr({addr}); - }); + auto loadImageFilterEdit = new FilterEdit(loadImageTable); + connect(loadImageFilterEdit, &FilterEdit::textChanged, [loadImageTable](const QString& filter) { + loadImageTable->setFilter(filter.toStdString()); + }); + + connect(loadImageTable, &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); - auto buttonLayout = new QHBoxLayout; - buttonLayout->addWidget(loadImageButton); - buttonLayout->addWidget(refreshDataButton); - buttonLayout->setAlignment(Qt::AlignLeft); - loadImageLayout->addLayout(buttonLayout); + auto loadImageLayout = new QVBoxLayout; + loadImageLayout->addWidget(loadImageFilterEdit); + loadImageLayout->addWidget(loadImageTable); - auto loadImageWidget = new QWidget; - loadImageWidget->setLayout(loadImageLayout); + auto buttonLayout = new QHBoxLayout; + buttonLayout->addWidget(loadImageButton); + buttonLayout->addWidget(refreshDataButton); + buttonLayout->setAlignment(Qt::AlignLeft); + loadImageLayout->addLayout(buttonLayout); - loadImageTable->setModel(m_imageModel); + auto loadImageWidget = new QWidget; + loadImageWidget->setLayout(loadImageLayout); - loadImageTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + loadImageTable->setModel(m_imageModel); - loadImageTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); - loadImageTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); - loadImageTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); + loadImageTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - loadImageTable->setSelectionBehavior(QAbstractItemView::SelectRows); - loadImageTable->setSelectionMode(QAbstractItemView::ExtendedSelection); + loadImageTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); + loadImageTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + loadImageTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); - loadImageTable->setSortingEnabled(true); + loadImageTable->setSelectionBehavior(QAbstractItemView::SelectRows); + loadImageTable->setSelectionMode(QAbstractItemView::ExtendedSelection); - loadImageTable->verticalHeader()->setVisible(false); + loadImageTable->setSortingEnabled(true); - m_triageTabs->addTab(loadImageWidget, "Images"); - defaultWidget = loadImageWidget; - m_triageTabs->setCanCloseTab(loadImageWidget, false); - } // loadImageTable + loadImageTable->verticalHeader()->setVisible(false); - // Tab: Symbols + m_triageTabs->addTab(loadImageWidget, "Images"); + m_triageTabs->setCanCloseTab(loadImageWidget, false); + + return loadImageWidget; // For use as the default widget +} + + +void DSCTriageView::initSymbolTable() +{ m_symbolTable = new SymbolTableView(this); - { - 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 symbolFilterEdit = new FilterEdit(m_symbolTable); + connect(symbolFilterEdit, &FilterEdit::textChanged, [this](const QString& filter) { + m_symbolTable->setFilter(filter.toStdString()); + }); - auto loadSymbolImageButton = new QPushButton(); - { - connect(loadSymbolImageButton, &QPushButton::clicked, - [this, loadImagesWithAddr](bool) { + // 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 + loadSymbolImageButton->setText("Load Image"); + } // loadImageButton - // 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(""); - }); + // Shows the current selected rows image name. + auto currentImageLabel = new QLabel(this); { + currentImageLabel->setText(""); + currentImageLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + } - auto symbolFooterLayout = new QHBoxLayout; - symbolFooterLayout->addWidget(loadSymbolImageButton); - symbolFooterLayout->addWidget(currentImageLabel); + // 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(""); + }); - symbolFooterLayout->setAlignment(Qt::AlignLeft); + auto symbolFooterLayout = new QHBoxLayout; + symbolFooterLayout->addWidget(loadSymbolImageButton); + symbolFooterLayout->addWidget(currentImageLabel); - auto symbolLayout = new QVBoxLayout; - symbolLayout->addWidget(symbolFilterEdit); - symbolLayout->addWidget(m_symbolTable); - symbolLayout->addLayout(symbolFooterLayout); + symbolFooterLayout->setAlignment(Qt::AlignLeft); - auto symbolWidget = new QWidget; - symbolWidget->setLayout(symbolLayout); + auto symbolLayout = new QVBoxLayout; + symbolLayout->addWidget(symbolFilterEdit); + symbolLayout->addWidget(m_symbolTable); + symbolLayout->addLayout(symbolFooterLayout); - connect(m_symbolTable, &SymbolTableView::activated, this, [=](const QModelIndex& index){ - auto symbol = m_symbolTable->getSymbolAtRow(index.row()); - auto dialog = new QMessageBox(this); + auto symbolWidget = new QWidget; + symbolWidget->setLayout(symbolLayout); - auto controller = SharedCacheController::GetController(*this->m_data); - if (!controller) - return; + connect(m_symbolTable, &SymbolTableView::activated, this, [=](const QModelIndex& index){ + auto symbol = m_symbolTable->getSymbolAtRow(index.row()); + auto dialog = new QMessageBox(this); - auto image = controller->GetImageContaining(symbol.address); - if (!image.has_value()) - return; + auto controller = SharedCacheController::GetController(*this->m_data); + if (!controller) + return; - dialog->setText("Load " + QString::fromStdString(image->name) + "?"); - dialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No); + auto image = controller->GetImageContaining(symbol.address); + if (!image.has_value()) + return; - connect(dialog, &QMessageBox::buttonClicked, this, [=](QAbstractButton* button) - { - if (button == dialog->button(QMessageBox::Yes)) - loadImagesWithAddr({image->headerAddress}); - }); + dialog->setText("Load " + QString::fromStdString(image->name) + "?"); + dialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No); - dialog->exec(); - }); + connect(dialog, &QMessageBox::buttonClicked, this, [=](QAbstractButton* button) + { + if (button == dialog->button(QMessageBox::Yes)) + loadImagesWithAddr({image->headerAddress}); + }); - m_triageTabs->addTab(symbolWidget, "Symbols"); - m_triageTabs->setCanCloseTab(symbolWidget, false); - } // symbolSearch + dialog->exec(); + }); - // Tab: Mappings & Regions - auto cacheInfoWidget = new QWidget; - { - auto cacheInfoLayout = new QVBoxLayout(cacheInfoWidget); + m_triageTabs->addTab(symbolWidget, "Symbols"); + m_triageTabs->setCanCloseTab(symbolWidget, false); +} - auto cacheInfoSubwidget = new QWidget; - auto mappingTable = new FilterableTableView(cacheInfoSubwidget); - m_mappingModel = new QStandardItemModel(0, 4, mappingTable); - m_mappingModel->setHorizontalHeaderLabels({"Address", "Size", "File Address", "File Path"}); +void DSCTriageView::initCacheInfoTables() +{ + auto cacheInfoWidget = new QWidget; - // Apply custom column styling - mappingTable->setItemDelegateForColumn(0, new AddressColorDelegate(mappingTable)); + auto cacheInfoLayout = new QVBoxLayout(cacheInfoWidget); - mappingTable->setModel(m_mappingModel); + auto cacheInfoSubwidget = new QWidget; - mappingTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); - mappingTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed); - mappingTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents); - mappingTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); + auto mappingTable = new FilterableTableView(cacheInfoSubwidget); + m_mappingModel = new QStandardItemModel(0, 4, mappingTable); + m_mappingModel->setHorizontalHeaderLabels({"Address", "Size", "File Address", "File Path"}); - mappingTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + // Apply custom column styling + mappingTable->setItemDelegateForColumn(0, new AddressColorDelegate(mappingTable)); - mappingTable->setSelectionBehavior(QAbstractItemView::SelectRows); - mappingTable->setSelectionMode(QAbstractItemView::ExtendedSelection); + mappingTable->setModel(m_mappingModel); - mappingTable->setSortingEnabled(true); + mappingTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); + mappingTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed); + mappingTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents); + mappingTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); - mappingTable->verticalHeader()->setVisible(false); + mappingTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - auto regionTable = new FilterableTableView(cacheInfoSubwidget); - m_regionModel = new QStandardItemModel(0, 4, regionTable); - m_regionModel->setHorizontalHeaderLabels({"Address", "Size", "Type", "Name"}); + mappingTable->setSelectionBehavior(QAbstractItemView::SelectRows); + mappingTable->setSelectionMode(QAbstractItemView::ExtendedSelection); - // Apply custom column styling - regionTable->setItemDelegateForColumn(0, new AddressColorDelegate(regionTable)); + mappingTable->setSortingEnabled(true); - regionTable->setModel(m_regionModel); + mappingTable->verticalHeader()->setVisible(false); - regionTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); - regionTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed); - regionTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Interactive); - regionTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); + auto regionTable = new FilterableTableView(cacheInfoSubwidget); + m_regionModel = new QStandardItemModel(0, 4, regionTable); + m_regionModel->setHorizontalHeaderLabels({"Address", "Size", "Type", "Name"}); - regionTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + // Apply custom column styling + regionTable->setItemDelegateForColumn(0, new AddressColorDelegate(regionTable)); - regionTable->setSelectionBehavior(QAbstractItemView::SelectRows); - regionTable->setSelectionMode(QAbstractItemView::ExtendedSelection); + regionTable->setModel(m_regionModel); - regionTable->setSortingEnabled(true); + regionTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); + regionTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed); + regionTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Interactive); + regionTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); - regionTable->verticalHeader()->setVisible(false); + regionTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - auto mappingLabel = new QLabel("Mappings"); - auto mappingFilterEdit = new FilterEdit(mappingTable); - { - connect(mappingFilterEdit, &FilterEdit::textChanged, [mappingTable](const QString& filter) { - mappingTable->setFilter(filter.toStdString()); - }); - } + regionTable->setSelectionBehavior(QAbstractItemView::SelectRows); + regionTable->setSelectionMode(QAbstractItemView::ExtendedSelection); - auto mappingHeaderLayout = new QHBoxLayout; - mappingHeaderLayout->addWidget(mappingLabel); - mappingHeaderLayout->addWidget(mappingFilterEdit); - mappingHeaderLayout->setAlignment(Qt::AlignJustify); - mappingHeaderLayout->setSpacing(30); + regionTable->setSortingEnabled(true); - auto regionLabel = new QLabel("Regions"); - auto regionFilterEdit = new FilterEdit(regionTable); - { - connect(regionFilterEdit, &FilterEdit::textChanged, [regionTable](const QString& filter) { - regionTable->setFilter(filter.toStdString()); - }); - } + regionTable->verticalHeader()->setVisible(false); - auto regionHeaderLayout = new QHBoxLayout; - regionHeaderLayout->addWidget(regionLabel); - regionHeaderLayout->addWidget(regionFilterEdit); - regionHeaderLayout->setAlignment(Qt::AlignJustify); - regionHeaderLayout->setSpacing(30); + auto mappingLabel = new QLabel("Mappings"); + auto mappingFilterEdit = new FilterEdit(mappingTable); + { + connect(mappingFilterEdit, &FilterEdit::textChanged, [mappingTable](const QString& filter) { + mappingTable->setFilter(filter.toStdString()); + }); + } - auto mappingLayout = new QVBoxLayout; - mappingLayout->addLayout(mappingHeaderLayout); - mappingLayout->addWidget(mappingTable); + auto mappingHeaderLayout = new QHBoxLayout; + mappingHeaderLayout->addWidget(mappingLabel); + mappingHeaderLayout->addWidget(mappingFilterEdit); + mappingHeaderLayout->setAlignment(Qt::AlignJustify); + mappingHeaderLayout->setSpacing(30); - auto regionLayout = new QVBoxLayout; - regionLayout->addLayout(regionHeaderLayout); - regionLayout->addWidget(regionTable); + auto regionLabel = new QLabel("Regions"); + auto regionFilterEdit = new FilterEdit(regionTable); + { + connect(regionFilterEdit, &FilterEdit::textChanged, [regionTable](const QString& filter) { + regionTable->setFilter(filter.toStdString()); + }); + } - cacheInfoLayout->addLayout(mappingLayout); - cacheInfoLayout->addLayout(regionLayout); + auto regionHeaderLayout = new QHBoxLayout; + regionHeaderLayout->addWidget(regionLabel); + regionHeaderLayout->addWidget(regionFilterEdit); + regionHeaderLayout->setAlignment(Qt::AlignJustify); + regionHeaderLayout->setSpacing(30); - m_triageTabs->addTab(cacheInfoWidget, "Mappings & Regions"); - m_triageTabs->setCanCloseTab(cacheInfoWidget, false); - } // cacheInfoSection + auto mappingLayout = new QVBoxLayout; + mappingLayout->addLayout(mappingHeaderLayout); + mappingLayout->addWidget(mappingTable); - m_layout = new QVBoxLayout(this); - m_layout->addWidget(m_triageTabs); - setLayout(m_layout); + auto regionLayout = new QVBoxLayout; + regionLayout->addLayout(regionHeaderLayout); + regionLayout->addWidget(regionTable); - // In case we have already initialized the controller (user has opened this view type again) - // we will call refresh data. If this is the first triage view constructed (i.e. before view init) then this - // will do nothing. - RefreshData(); + cacheInfoLayout->addLayout(mappingLayout); + cacheInfoLayout->addLayout(regionLayout); - m_triageTabs->selectWidget(defaultWidget); + m_triageTabs->addTab(cacheInfoWidget, "Mappings & Regions"); + m_triageTabs->setCanCloseTab(cacheInfoWidget, false); } -DSCTriageView::~DSCTriageView() -{ - UIContext::unregisterNotification(this); -} QFont DSCTriageView::getFont() { return getMonospaceFont(this); } + BinaryViewRef DSCTriageView::getData() { return m_data; } + bool DSCTriageView::navigate(uint64_t offset) { // TODO: We have to set this to true otherwise view restore does not pickup this view. return true; } + uint64_t DSCTriageView::getCurrentOffset() { return 0; } + SelectionInfoForXref DSCTriageView::getSelectionForXref() { // TODO: If we are in the symbols view we _can_ actually show a useful xref to the selected symbols. @@ -461,12 +477,14 @@ SelectionInfoForXref DSCTriageView::getSelectionForXref() return selection; } + void DSCTriageView::OnAfterOpenFile(UIContext *context, FileContext *file, ViewFrame *frame) { RefreshData(); UIContextNotification::OnAfterOpenFile(context, file, frame); } + // Called when shared cache information has changed. void DSCTriageView::RefreshData() { @@ -517,6 +535,7 @@ void DSCTriageView::RefreshData() m_symbolTable->populateSymbols(*m_data); } + void DSCTriageView::setImageLoaded(const uint64_t imageHeaderAddr) { // Go through the m_loadImageModel and find the image associated with the address diff --git a/view/sharedcache/ui/dsctriage.h b/view/sharedcache/ui/dsctriage.h index b1e98f6d..a16457e3 100644 --- a/view/sharedcache/ui/dsctriage.h +++ b/view/sharedcache/ui/dsctriage.h @@ -1,7 +1,3 @@ -// -// Created by kat on 8/15/24. -// - #include <binaryninjaapi.h> #include <QItemDelegate> #include <QStyledItemDelegate> @@ -19,6 +15,7 @@ #ifndef BINARYNINJA_DSCTRIAGE_H #define BINARYNINJA_DSCTRIAGE_H + class AddressColorDelegate : public QStyledItemDelegate { @@ -37,6 +34,65 @@ public: } }; + +class LoadedDelegate : public QItemDelegate +{ +Q_OBJECT + +public: + explicit LoadedDelegate(QObject* parent = nullptr) : QItemDelegate(parent) {} + + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const override + { + if (!index.isValid()) + return; + + painter->save(); + + // Highlight if the item is selected + if (option.state & QStyle::State_Selected) + painter->fillRect(option.rect, option.palette.highlight()); + + // "1" is the indicator that its loaded. + if (index.data(Qt::DisplayRole).toString() == "1") + { + QPixmap loadedIcon; + pixmapForBWMaskIcon(":/icons/images/check.png", &loadedIcon, SidebarHeaderTextColor); + if (!loadedIcon.isNull()) + { + QSize pixmapSize(20, 20); + QPixmap scaledPixmap = loadedIcon.scaled(pixmapSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + + // Calculate the rectangle for centering the pixmap + int x = option.rect.x() + (option.rect.width() - scaledPixmap.width()) / 2; // Center horizontally + int y = option.rect.y() + (option.rect.height() - scaledPixmap.height()) / 2; // Center vertically + QRect iconRect(x, y, scaledPixmap.width(), scaledPixmap.height()); + + // Draw the pixmap + painter->drawPixmap(iconRect, scaledPixmap); + } + } + + painter->restore(); + } + + QSize sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const override + { + Q_UNUSED(option); + Q_UNUSED(index); + return {50, 24}; + } + + void setEditorData(QWidget *editor, const QModelIndex &index) const override + { + Q_UNUSED(editor); + Q_UNUSED(index); + } +}; + + class FilterableTableView : public QTableView, public FilterTarget { Q_OBJECT @@ -119,6 +175,7 @@ signals: void filterTextChanged(const QString& text); }; + class DSCTriageView : public QWidget, public View, public UIContextNotification { BinaryViewRef m_data; @@ -148,6 +205,12 @@ public: void OnAfterOpenFile(UIContext* context, FileContext* file, ViewFrame* frame) override; void RefreshData(); void setImageLoaded(uint64_t imageHeaderAddr); + +private: + void loadImagesWithAddr(const std::vector<uint64_t>& addresses, bool includeDependencies = false); + QWidget* initImageTable(); + void initSymbolTable(); + void initCacheInfoTables(); }; @@ -160,61 +223,4 @@ public: static void Register(); }; -class LoadedDelegate : public QItemDelegate -{ - Q_OBJECT - -public: - explicit LoadedDelegate(QObject* parent = nullptr) : QItemDelegate(parent) {} - - void paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const override - { - if (!index.isValid()) - return; - - painter->save(); - - // Highlight if the item is selected - if (option.state & QStyle::State_Selected) - painter->fillRect(option.rect, option.palette.highlight()); - - // "1" is the indicator that its loaded. - if (index.data(Qt::DisplayRole).toString() == "1") - { - QPixmap loadedIcon; - pixmapForBWMaskIcon(":/icons/images/check.png", &loadedIcon, SidebarHeaderTextColor); - if (!loadedIcon.isNull()) - { - QSize pixmapSize(20, 20); - QPixmap scaledPixmap = loadedIcon.scaled(pixmapSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); - - // Calculate the rectangle for centering the pixmap - int x = option.rect.x() + (option.rect.width() - scaledPixmap.width()) / 2; // Center horizontally - int y = option.rect.y() + (option.rect.height() - scaledPixmap.height()) / 2; // Center vertically - QRect iconRect(x, y, scaledPixmap.width(), scaledPixmap.height()); - - // Draw the pixmap - painter->drawPixmap(iconRect, scaledPixmap); - } - } - - painter->restore(); - } - - QSize sizeHint(const QStyleOptionViewItem &option, - const QModelIndex &index) const override - { - Q_UNUSED(option); - Q_UNUSED(index); - return {50, 24}; - } - - void setEditorData(QWidget *editor, const QModelIndex &index) const override - { - Q_UNUSED(editor); - Q_UNUSED(index); - } -}; - #endif // BINARYNINJA_DSCTRIAGE_H diff --git a/view/sharedcache/ui/symboltable.cpp b/view/sharedcache/ui/symboltable.cpp index 0b006fbc..1df7d529 100644 --- a/view/sharedcache/ui/symboltable.cpp +++ b/view/sharedcache/ui/symboltable.cpp @@ -102,7 +102,8 @@ void SymbolTableModel::setFilter(std::string text) if (((std::string_view)symbol.name).find(m_filter) != std::string::npos) m_modelSymbols.push_back(symbol); m_modelSymbols.shrink_to_fit(); - } else + } + else { m_modelSymbols = m_preparedSymbols; } @@ -112,7 +113,7 @@ void SymbolTableModel::setFilter(std::string text) SymbolTableView::SymbolTableView(QWidget* parent) - : m_model(new SymbolTableModel(this)) { + : QTableView(parent), m_model(new SymbolTableModel(this)) { // Set up the filter model setModel(m_model); |
