summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Taylor <alex@vector35.com>2025-04-09 12:47:00 -0400
committerAlexander Taylor <alex@vector35.com>2025-04-09 17:16:23 -0400
commite3772366504d6d05b4809013cb4f4149ca64c4d4 (patch)
treee3fcb0818bfede1a5f8fff0b1edd59422b517b67
parentf78345462ebc25e0241d838f04104e5c6bfae863 (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.cpp548
-rw-r--r--view/kernelcache/ui/kctriage.h79
-rw-r--r--view/sharedcache/ui/dsctriage.cpp621
-rw-r--r--view/sharedcache/ui/dsctriage.h128
-rw-r--r--view/sharedcache/ui/symboltable.cpp5
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 &current, 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 &current, 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);