summaryrefslogtreecommitdiff
path: root/view/kernelcache/ui
diff options
context:
space:
mode:
authorkat <kat@vector35.com>2025-07-06 15:05:01 -0400
committerkat <kat@vector35.com>2025-07-07 07:37:23 -0400
commit768f7c78465fb93936e5ca50a0ca712664fe54e7 (patch)
tree78c73e0022d6006882e365a257595a5b55a37432 /view/kernelcache/ui
parent8f3e251c42169fb4fe8db9403d900571434d88ff (diff)
KernelCache rewrite
Diffstat (limited to 'view/kernelcache/ui')
-rw-r--r--view/kernelcache/ui/KernelCacheUINotifications.cpp133
-rw-r--r--view/kernelcache/ui/kctriage.cpp258
-rw-r--r--view/kernelcache/ui/kctriage.h198
-rw-r--r--view/kernelcache/ui/symboltable.cpp216
-rw-r--r--view/kernelcache/ui/symboltable.h102
5 files changed, 580 insertions, 327 deletions
diff --git a/view/kernelcache/ui/KernelCacheUINotifications.cpp b/view/kernelcache/ui/KernelCacheUINotifications.cpp
index 6bd8fdf0..9d7e17a9 100644
--- a/view/kernelcache/ui/KernelCacheUINotifications.cpp
+++ b/view/kernelcache/ui/KernelCacheUINotifications.cpp
@@ -3,13 +3,15 @@
//
#include "KernelCacheUINotifications.h"
-#include <QLayout>
#include <kernelcacheapi.h>
#include "ui/sidebar.h"
#include "ui/linearview.h"
#include "ui/viewframe.h"
#include "progresstask.h"
+using namespace BinaryNinja;
+using namespace KernelCacheAPI;
+
UINotifications* UINotifications::m_instance = nullptr;
void UINotifications::init()
@@ -18,75 +20,94 @@ void UINotifications::init()
UIContext::registerNotification(m_instance);
}
-
void UINotifications::OnViewChange(UIContext* context, ViewFrame* frame, const QString& type)
{
if (!frame)
return;
- // FIXME there is a bv func for this
- static std::function<bool(Ref<BinaryView>, uint64_t)> isAddrMapped = [](Ref<BinaryView> view, uint64_t addr) {
- if (view && view->GetTypeName() == KC_VIEW_NAME)
+ auto view = frame->getCurrentBinaryView();
+ if (!view || view->GetTypeName() != KC_VIEW_NAME)
+ return;
+
+ auto viewInt = frame->getCurrentViewInterface();
+ if (!viewInt)
+ return;
+
+ auto ah = viewInt->actionHandler();
+ // Check to see if we have already bound these actions.
+ if (ah->isBoundAction("Load Image by Name"))
+ return;
+
+ static auto loadImageAtAddr = [](BinaryView& view, uint64_t addr) {
+ auto controller = KernelCacheController::GetController(view);
+ if (!controller)
+ return;
+ if (auto foundImage = controller->GetImageContaining(addr))
{
- for (const auto& seg : view->GetSegments())
- {
- if (seg->GetStart() <= addr && seg->GetEnd() > addr)
- return true;
- }
+ // If we did not load the image, then we don't need to run analysis.
+ if (!controller->ApplyImage(view, *foundImage))
+ return;
+ view.AddAnalysisOption("linearsweep");
+ view.UpdateAnalysis();
}
- return false;
};
- auto view = frame->getCurrentBinaryView();
- if (view && view->GetTypeName() == KC_VIEW_NAME)
- {
- if (auto viewInt = frame->getCurrentViewInterface())
+ auto loadImageAddrAction = [](const UIActionContext& ctx) {
+ uint64_t addr = 0;
+ if (GetAddressInput(addr, "Address", "Address"))
{
- auto ah = viewInt->actionHandler();
- if (!ah->isBoundAction("KC Load IMGHERE"))
- {
- ah->bindAction("KC Load IMGHERE",
- UIAction(
- [](const UIActionContext& ctx) {
- Ref<BinaryView> view = ctx.binaryView;
- Ref<KernelCacheAPI::KernelCache> cache = new KernelCacheAPI::KernelCache(view);
- uint64_t addr = ctx.token.token.value;
- if (addr)
- {
- BackgroundThread::create(ctx.context->mainWindow())->thenBackground(
- [cache=cache, addr=addr]() {
- cache->LoadImageContainingAddress(addr);
- })->start();
- }
- },
- [](const UIActionContext& ctx) {
- Ref<KernelCacheAPI::KernelCache> cache = new KernelCacheAPI::KernelCache(ctx.binaryView);
- uint64_t addr = ctx.token.token.value;
- if (isAddrMapped(ctx.binaryView, addr))
- return false;
- return addr && cache->GetImageNameForAddress(addr) != ""; // bool
- }));
- ah->setActionDisplayName("KC Load IMGHERE", [](const UIActionContext& ctx) {
- Ref<KernelCacheAPI::KernelCache> cache = new KernelCacheAPI::KernelCache(ctx.binaryView);
- uint64_t addr = ctx.token.token.value;
- if (addr)
- return QString("Load ") + cache->GetImageNameForAddress(addr).c_str();
- return QString("Error");
- });
- if (auto linearView = qobject_cast<LinearView*>(viewInt->widget()))
- {
- linearView->contextMenu().addAction("KC Load IMGHERE", KC_VIEW_NAME);
- linearView->contextMenu().setGroupOrdering(KC_VIEW_NAME, 0);
- }
- }
+ BackgroundThread::create(ctx.context->mainWindow())
+ ->thenBackground([ctx, addr]() {
+ loadImageAtAddr(*ctx.binaryView, addr);
+ })->start();
}
+ };
+
+ auto loadImageTokenAction = [](const UIActionContext& ctx) {
+ BackgroundThread::create(ctx.context->mainWindow())
+ ->thenBackground([ctx](){ loadImageAtAddr(*ctx.binaryView, ctx.token.token.value); })
+ ->start();
+ };
+
+ auto isValidUnloadedImageAction = [](const UIActionContext& ctx) {
+ uint64_t addr = ctx.token.token.value;
+ // Check if the image is already loaded in the view.
+ if (!ctx.binaryView->GetSectionsAt(addr).empty())
+ return false;
+ auto controller = KernelCacheController::GetController(*ctx.binaryView);
+ if (!controller)
+ return false;
+ return controller->GetImageContaining(addr).has_value();
+ };
+
+ ah->bindAction("Load Image by Address", UIAction(loadImageAddrAction));
+
+ ah->bindAction("Load IMGHERE", UIAction(loadImageTokenAction, isValidUnloadedImageAction));
+
+ ah->setActionDisplayName("Load IMGHERE", [](const UIActionContext& ctx) {
+ auto controller = KernelCacheController::GetController(*ctx.binaryView);
+ if (!controller)
+ return QString("NO CONTROLLER");
+ uint64_t addr = ctx.token.token.value;
+ auto image = controller->GetImageContaining(addr);
+ if (!image)
+ return QString("NO IMAGE");
+ return QString("Load ") + image->name.c_str();
+ });
+
+ // Finally add the actions to the context menu.
+ if (auto linearView = qobject_cast<LinearView*>(viewInt->widget()))
+ {
+ constexpr auto groupOneName = KC_VIEW_NAME;
+ constexpr auto groupTwoName = KC_VIEW_NAME "2";
+ linearView->contextMenu().addAction("Load IMGHERE", groupOneName);
+ linearView->contextMenu().addAction("Load Image by Address", groupTwoName);
+ linearView->contextMenu().setGroupOrdering(groupOneName, 0);
+ linearView->contextMenu().setGroupOrdering(groupTwoName, 1);
}
}
+
void UINotifications::OnAfterOpenFile(UIContext* context, FileContext* file, ViewFrame* frame)
{
- if (frame->getCurrentBinaryView())
- {
- // Register BD notifications. We dont use them right now.
- }
UIContextNotification::OnAfterOpenFile(context, file, frame);
}
diff --git a/view/kernelcache/ui/kctriage.cpp b/view/kernelcache/ui/kctriage.cpp
index c28c323a..da2f2270 100644
--- a/view/kernelcache/ui/kctriage.cpp
+++ b/view/kernelcache/ui/kctriage.cpp
@@ -1,8 +1,9 @@
+#include <QHeaderView>
#include <QMessageBox>
-#include <QPainter>
-#include <cmath>
-#include "globalarea.h"
+#include <utility>
#include "kctriage.h"
+#include "globalarea.h"
+#include "symboltable.h"
#include "ui/fontsettings.h"
using namespace BinaryNinja;
@@ -36,7 +37,7 @@ void KCTriageViewType::Register()
}
-KCTriageView::KCTriageView(QWidget* parent, BinaryViewRef data) : QWidget(parent), View(), m_data(data), m_cache(new KernelCache(data))
+KCTriageView::KCTriageView(QWidget* parent, BinaryViewRef data) : QWidget(parent), m_data(std::move(data))
{
setBinaryDataNavigable(false);
setupView(this);
@@ -56,6 +57,11 @@ KCTriageView::KCTriageView(QWidget* parent, BinaryViewRef data) : QWidget(parent
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);
}
@@ -66,43 +72,88 @@ KCTriageView::~KCTriageView()
}
-void KCTriageView::loadImagesWithAddr(const std::vector<uint64_t>& addresses) {
- if (!m_cache)
+void KCTriageView::loadImagesWithAddr(const std::vector<uint64_t>& addresses, bool includeDependencies) {
+ auto controller = KernelCacheController::GetController(*m_data);
+ if (!controller)
return;
- std::map<uint64_t, std::string> images;
+ // TODO: NOTE ABOUT `IsImageLoaded` BEING COMMENTED OUT. PLEASE READ.
+ // TODO: Because commiting undo actions will use main thread to synchronize we must not be holding any locks
+ // TODO: This can really only ever be removed if:
+ // TODO: 1. we can set a user function type without creating an undo action, basically like the rest of shared cache
+ // TODo: use an auto function or some hack to get the user function but without the undo action.
+ // TODO: 2. we can use the undo buffer from any thread and not just the main thread
+ // TODO: I have exhausted all other options, this is a serious issue we should address soon.
+ typedef std::vector<CacheImage> ImageList;
+ ImageList images = {};
for (const uint64_t& addr : addresses)
{
- auto imageName = m_cache->GetImageNameForAddress(addr);
- if (!imageName.empty() && !m_cache->IsImageLoaded(addr))
+ std::optional<CacheImage> image = controller->GetImageContaining(addr);
+ if (image.has_value())
{
- images.insert({addr, imageName});
+ // Only try to load if we have not already.
+ // if (!controller->IsImageLoaded(*image))
+ images.emplace_back(*image);
+
+ // 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 depImage = controller->GetImageWithName(depName);
+ if (depImage.has_value()/* && !controller->IsImageLoaded(*depImage) */)
+ {
+ images.emplace_back(*depImage);
+ }
+ }
+ }
}
}
// Don't create a worker action if we don't have any images.
if (images.empty())
return;
+ Ref<BackgroundTask> imageLoadTask = new BackgroundTask("Loading images...", true);
+
+ // Apply the images in a future then update the triage view and run analysis.
+ QPointer<QFutureWatcher<ImageList>> watcher = new QFutureWatcher<ImageList>(this);
+ connect(watcher, &QFutureWatcher<ImageList>::finished, this, [watcher, this]() {
+ if (watcher)
+ {
+ auto loadedImages = watcher->result();
+ if (loadedImages.empty())
+ return;
- WorkerPriorityEnqueue([this, images]() {
- size_t loadedImages = 0;
- const std::string initialLoad = fmt::format("Loading images... (0/{})", images.size());
- auto imageLoadTask = BackgroundTask(initialLoad, true);
+ // Update the triage to display the images as loaded.
+ for (const auto& image : loadedImages)
+ setImageLoaded(image.headerVirtualAddress);
- for (const auto& [addr, imageName] : images)
+ // Run analysis.
+ this->m_data->AddAnalysisOption("linearsweep");
+ this->m_data->UpdateAnalysis();
+ }
+ });
+ QFuture<ImageList> future = QtConcurrent::run([this, controller, images, imageLoadTask]() {
+ ImageList loadedImages = {};
+ for (const auto& image : images)
{
- if (imageLoadTask.IsCancelled())
+ if (imageLoadTask->IsCancelled() || QThread::currentThread()->isInterruptionRequested())
break;
- const std::string newLoad = fmt::format("Loading images... ({}/{})", loadedImages++, images.size());
- imageLoadTask.SetProgressText(newLoad);
- if (m_cache->LoadImageWithInstallName(imageName))
- setImageLoaded(addr);
+ std::string newLoad = fmt::format("Loading images... ({}/{})", loadedImages.size(), images.size());
+ imageLoadTask->SetProgressText(newLoad);
+ if (controller->ApplyImage(*this->m_data, image))
+ loadedImages.emplace_back(image);
+ }
+ imageLoadTask->Finish();
+ return loadedImages;
+ });
+ watcher->setFuture(future);
+ connect(this, &QObject::destroyed, this, [watcher, imageLoadTask]() {
+ if (watcher && watcher->isRunning()) {
+ watcher->cancel();
+ imageLoadTask->Cancel();
}
- imageLoadTask.Finish();
-
- // We have loaded images, lets make sure to update analysis!
- this->m_data->AddAnalysisOption("linearsweep");
- this->m_data->UpdateAnalysis();
});
}
@@ -131,7 +182,7 @@ QWidget* KCTriageView::initImageTable()
m_imageTable = new FilterableTableView(this);
m_imageModel = new QStandardItemModel(0, 3, m_imageTable);
- m_imageModel->setHorizontalHeaderLabels({"VM Address", "Loaded", "Name"});
+ m_imageModel->setHorizontalHeaderLabels({"Address", "Loaded", "Name"});
// Apply custom column styling
m_imageTable->setItemDelegateForColumn(0, new AddressColorDelegate(m_imageTable));
@@ -157,6 +208,7 @@ QWidget* KCTriageView::initImageTable()
QAction noSelectionAction("No Images Selected", m_imageTable);
QAction loadImagesAction("", m_imageTable);
+ QAction loadImagesWithDepsAction("", m_imageTable);
if (selectedCount == 0)
{
noSelectionAction.setEnabled(false);
@@ -168,57 +220,22 @@ QWidget* KCTriageView::initImageTable()
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);
+ 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, [this, addresses]() {
+ this->loadImagesWithAddr(addresses, true);
+ });
+ contextMenu.addAction(&loadImagesWithDepsAction);
}
contextMenu.exec(m_imageTable->viewport()->mapToGlobal(pos));
});
- BackgroundThread::create(m_imageTable)->thenBackground([this](const QVariant var) {
- QVariantList rows;
-
- auto images = m_cache->GetImages();
-
- auto newHeaders = std::make_shared<std::vector<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("0x%1").arg(header->textBase, 0, 16),
- QString(""),
- QString::fromStdString(img.name)
- });
- }
- }
-
- std::unique_lock<std::mutex> lock(m_headersMutex);
- m_headers.swap(newHeaders);
-
- return QVariant(rows);
- })->thenMainThread([this](const QVariant var) {
- QVariantList rows = var.toList();
-
- if (m_imageModel->rowCount() > 0)
- m_imageModel->removeRows(0, m_imageModel->rowCount());
-
- for (const QVariant &rowVariant : rows) {
- QVariantList row = rowVariant.toList();
-
- QList<QStandardItem*> items;
- for (const QVariant &cellValue : row)
- items.append(new QStandardItem(cellValue.toString()));
-
- m_imageModel->appendRow(items);
- m_imageTable->resizeColumnsToContents();
- }
- })->start();
-
auto loadImageButton = new QPushButton();
connect(loadImageButton, &QPushButton::clicked, [this](bool) {
// Collect only visible selected rows
@@ -239,6 +256,13 @@ QWidget* KCTriageView::initImageTable()
});
loadImageButton->setText(" Load Selected ");
+ 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
+
auto loadImageFilterEdit = new FilterEdit(m_imageTable);
connect(loadImageFilterEdit, &FilterEdit::textChanged, [this](const QString& filter) {
m_imageTable->setFilter(filter.toStdString());
@@ -255,6 +279,7 @@ QWidget* KCTriageView::initImageTable()
auto loadImageFooterLayout = new QHBoxLayout;
loadImageFooterLayout->addWidget(loadImageButton);
+ loadImageFooterLayout->addWidget(refreshDataButton);
loadImageFooterLayout->setAlignment(Qt::AlignLeft);
loadImageLayout->addLayout(loadImageFooterLayout);
@@ -285,7 +310,10 @@ QWidget* KCTriageView::initImageTable()
void KCTriageView::initSymbolTable()
{
- m_symbolTable = new SymbolTableView(this, m_cache);
+ m_symbolTable = new SymbolTableView(this);
+
+ // Apply custom column styling
+ m_symbolTable->setItemDelegateForColumn(0, new AddressColorDelegate(m_symbolTable));
auto symbolFilterEdit = new FilterEdit(m_symbolTable);
connect(symbolFilterEdit, &FilterEdit::textChanged, [this](const QString& filter) {
@@ -308,8 +336,14 @@ void KCTriageView::initSymbolTable()
currentImageLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
connect(m_symbolTable->selectionModel(), &QItemSelectionModel::currentRowChanged, this, [this, currentImageLabel](const QModelIndex &current, const QModelIndex &) {
auto symbol = m_symbolTable->getSymbolAtRow(current.row());
- auto imageName = m_cache->GetImageNameForAddress(symbol.address);
- currentImageLabel->setText("Image: " + QString::fromStdString(imageName));
+ auto controller = KernelCacheController::GetController(*this->m_data);
+ if (!controller)
+ return;
+ auto image = controller->GetImageContaining(symbol.address);
+ if (image)
+ currentImageLabel->setText("Image: " + QString::fromStdString(image->name));
+ else
+ currentImageLabel->setText("");
});
auto symbolFooterLayout = new QHBoxLayout;
@@ -325,27 +359,29 @@ void KCTriageView::initSymbolTable()
auto symbolWidget = new QWidget;
symbolWidget->setLayout(symbolLayout);
- std::function<void(uint64_t)> navigateToAddress = [=, this](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);
- });
- };
-
connect(m_symbolTable, &SymbolTableView::activated, this, [=, this](const QModelIndex& index)
{
auto symbol = m_symbolTable->getSymbolAtRow(index.row());
- WorkerPriorityEnqueue([this, symbol, navigateToAddress]() {
- if (m_data->IsValidOffset(symbol.address))
- navigateToAddress(symbol.address);
- else
+ auto dialog = new QMessageBox(this);
+
+ auto controller = KernelCacheController::GetController(*this->m_data);
+ if (!controller)
+ return;
+
+ auto image = controller->GetImageContaining(symbol.address);
+ if (!image.has_value())
+ return;
+
+ dialog->setText("Load " + QString::fromStdString(image->name) + "?");
+ dialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
+
+ connect(dialog, &QMessageBox::buttonClicked, this, [=](QAbstractButton* button)
{
- m_cache->LoadImageWithInstallName(symbol.image);
- navigateToAddress(symbol.address);
- }
- });
+ if (button == dialog->button(QMessageBox::Yes))
+ loadImagesWithAddr({image->headerFileAddress});
+ });
+
+ dialog->exec();
});
m_triageTabs->addTab(symbolWidget, "Symbols");
@@ -375,3 +411,45 @@ uint64_t KCTriageView::getCurrentOffset()
{
return 0;
}
+
+
+SelectionInfoForXref KCTriageView::getSelectionForXref()
+{
+ // TODO: If we are in the symbols view we _can_ actually show a useful xref to the selected symbols.
+ SelectionInfoForXref selection = {};
+ selection.addrValid = false;
+ return selection;
+}
+
+
+void KCTriageView::OnAfterOpenFile(UIContext *context, FileContext *file, ViewFrame *frame)
+{
+ RefreshData();
+ UIContextNotification::OnAfterOpenFile(context, file, frame);
+}
+
+
+// Called when shared cache information has changed.
+void KCTriageView::RefreshData()
+{
+ // Controller should be available after view init.
+ auto controller = KernelCacheController::GetController(*m_data);
+ if (!controller)
+ return;
+
+ m_imageModel->setRowCount(0);
+ for (const auto& img : controller->GetImages())
+ {
+ m_imageModel->appendRow({
+ new QStandardItem(QString("0x%1").arg(img.headerVirtualAddress, 0, 16)),
+ new QStandardItem(""),
+ new QStandardItem(QString::fromStdString(img.name))
+ });
+ }
+
+ // Set images as loaded (updating the relevant image row)
+ for (const auto& loadedImg : controller->GetLoadedImages())
+ setImageLoaded(loadedImg.headerVirtualAddress);
+
+ m_symbolTable->populateSymbols(*m_data);
+}
diff --git a/view/kernelcache/ui/kctriage.h b/view/kernelcache/ui/kctriage.h
index 2bd106b6..d918cad6 100644
--- a/view/kernelcache/ui/kctriage.h
+++ b/view/kernelcache/ui/kctriage.h
@@ -6,18 +6,19 @@
#include <QStyledItemDelegate>
#include <QTableView>
#include <binaryninjaapi.h>
-#include <kernelcacheapi.h>
#include <progresstask.h>
+#include <kernelcacheapi.h>
#include "filter.h"
+#include "symboltable.h"
#include "ui/fontsettings.h"
#include "uicontext.h"
#include "uitypes.h"
#include "viewframe.h"
#ifndef BINARYNINJA_KCTRIAGE_H
-#define BINARYNINJA_KCTRIAGE_H
-
+ #define BINARYNINJA_KCTRIAGE_H
+using namespace BinaryNinja;
using namespace KernelCacheAPI;
@@ -40,30 +41,15 @@ public:
};
-class MonospaceFontDelegate : public QStyledItemDelegate {
-public:
- explicit MonospaceFontDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {}
-
- void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override {
- QStyleOptionViewItem opt = option;
- initStyleOption(&opt, index);
-
- opt.font = getMonospaceFont(qobject_cast<QWidget*>(parent()));
-
- QStyledItemDelegate::paint(painter, opt, index);
- }
-};
-
-
class LoadedDelegate : public QItemDelegate
{
-Q_OBJECT
+ Q_OBJECT
public:
explicit LoadedDelegate(QObject* parent = nullptr) : QItemDelegate(parent) {}
void paint(QPainter *painter, const QStyleOptionViewItem &option,
- const QModelIndex &index) const override
+ const QModelIndex &index) const override
{
if (!index.isValid())
return;
@@ -98,7 +84,7 @@ public:
}
QSize sizeHint(const QStyleOptionViewItem &option,
- const QModelIndex &index) const override
+ const QModelIndex &index) const override
{
Q_UNUSED(option);
Q_UNUSED(index);
@@ -113,7 +99,6 @@ public:
};
-
class FilterableTableView : public QTableView, public FilterTarget {
Q_OBJECT
@@ -190,165 +175,11 @@ signals:
};
-class SymbolTableProxyModel : public QSortFilterProxyModel
-{
-Q_OBJECT
-
-public:
- explicit SymbolTableProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent), m_timer(new QTimer(this))
- {
- m_timer->setSingleShot(true);
- connect(m_timer, &QTimer::timeout, this, &SymbolTableProxyModel::delayedFilterChanged);
- }
-
- void setFilterString(const QString& filter)
- {
- QRegularExpression newRegEx(QRegularExpression::escape(filter), QRegularExpression::CaseInsensitiveOption);
- if (m_filter != newRegEx) {
- m_filter = std::move(newRegEx);
- m_timer->start(200);
- }
- }
-
-protected:
- bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override
- {
- if (m_filter.pattern().isEmpty())
- return true;
-
- for (int column = 0; column < sourceModel()->columnCount(source_parent); ++column)
- {
- QModelIndex index = sourceModel()->index(source_row, column, source_parent);
- QString data = sourceModel()->data(index).toString();
- if (m_filter.match(data).hasMatch())
- return true;
- }
- return false;
- }
-
-private slots:
- void delayedFilterChanged()
- {
- invalidateFilter();
- }
-
-private:
- QRegularExpression m_filter;
- QTimer* m_timer;
-};
-
-
-class SymbolTableView : public QTableView, public FilterTarget
-{
-Q_OBJECT
- friend class SymbolTableModel;
-
- std::vector<KernelCacheAPI::KCSymbol> m_symbols;
- QStandardItemModel* m_model;
- SymbolTableProxyModel* m_proxyModel;
-
-public:
- SymbolTableView(QWidget* parent, Ref<KernelCache>& cache)
- : QTableView(parent), m_model(new QStandardItemModel(this)), m_proxyModel(new SymbolTableProxyModel(this))
- {
- m_proxyModel->setSourceModel(m_model);
- setModel(m_proxyModel);
-
- // Set up the headers
- m_model->setColumnCount(3);
- m_model->setHorizontalHeaderLabels({"Address", "Name", "Image"});
- setFont(getMonospaceFont(parent));
- setItemDelegateForColumn(0, new AddressColorDelegate(this));
- setItemDelegateForColumn(1, new MonospaceFontDelegate(this));
- setItemDelegateForColumn(2, new MonospaceFontDelegate(this));
-
- // Configure view settings
- horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
- horizontalHeader()->setSectionResizeMode(1, QHeaderView::Interactive);
- horizontalHeader()->resizeSection(1, 400);
- horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
- setEditTriggers(QAbstractItemView::NoEditTriggers);
- setSelectionBehavior(QAbstractItemView::SelectRows);
- setSelectionMode(QAbstractItemView::SingleSelection);
- verticalHeader()->setVisible(false);
-
- setSortingEnabled(true);
-
- BackgroundThread::create(this)->thenBackground([this, cache](){
- m_symbols = cache->LoadAllSymbolsAndWait();
- })->thenMainThread([this](){
- updateSymbols();
- })->start();
- }
-
- ~SymbolTableView() override = default;
-
- void updateSymbols()
- {
- m_model->removeRows(0, m_model->rowCount());
- for (const auto& symbol : m_symbols)
- {
- QList<QStandardItem*> row;
- row << new QStandardItem(QString("0x%1").arg(symbol.address, 0, 16))
- << new QStandardItem(QString::fromStdString(symbol.name))
- << new QStandardItem(QString::fromStdString(symbol.image));
- m_model->appendRow(row);
- }
- }
-
- KernelCacheAPI::KCSymbol getSymbolAtRow(int row) const
- {
- QModelIndex proxyIndex = m_proxyModel->index(row, 0);
- QModelIndex sourceIndex = m_proxyModel->mapToSource(proxyIndex);
- return m_symbols[sourceIndex.row()];
- }
-
- void scrollToFirstItem() override
- {
- scrollToTop();
- }
-
- void scrollToCurrentItem() override
- {
- scrollTo(selectionModel()->currentIndex());
- }
-
- void selectFirstItem() override
- {
- if (m_proxyModel->rowCount() > 0) {
- QModelIndex idx = m_proxyModel->index(0, 0);
- if (idx.isValid()) {
- selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect);
- setCurrentIndex(idx);
- }
- }
- }
-
- void activateFirstItem() override
- {
- if (m_proxyModel->rowCount() > 0) {
- QModelIndex idx = m_proxyModel->index(0, 0);
- if (idx.isValid()) {
- setCurrentIndex(idx);
- emit activated(idx);
- }
- }
- }
-
- void setFilter(const std::string& text) override
- {
- m_proxyModel->setFilterString(QString::fromStdString(text));
- }
-};
-
-
class KCTriageView : public QWidget, public View, public UIContextNotification
{
BinaryViewRef m_data;
QVBoxLayout* m_layout;
- Ref<KernelCacheAPI::KernelCache> m_cache;
-
SplitTabWidget* m_triageTabs;
DockableTabCollection* m_triageCollection;
@@ -357,8 +188,10 @@ class KCTriageView : public QWidget, public View, public UIContextNotification
SymbolTableView* m_symbolTable;
- std::mutex m_headersMutex;
- std::shared_ptr<std::vector<KernelCacheAPI::KernelCacheMachOHeader>> m_headers;
+ FilterableTableView* m_mappingTable;
+ QStandardItemModel* m_mappingModel;
+
+ QStandardItemModel* m_regionModel;
public:
KCTriageView(QWidget* parent, BinaryViewRef data);
@@ -368,10 +201,14 @@ public:
QFont getFont() override;
bool navigate(uint64_t offset) override;
uint64_t getCurrentOffset() override;
+ SelectionInfoForXref getSelectionForXref() override;
+
+ void OnAfterOpenFile(UIContext* context, FileContext* file, ViewFrame* frame) override;
+ void RefreshData();
private:
- void loadImagesWithAddr(const std::vector<uint64_t>& addresses);
- void setImageLoaded(const uint64_t imageHeaderAddr);
+ void loadImagesWithAddr(const std::vector<uint64_t>& addresses, bool includeDependencies = false);
+ void setImageLoaded(uint64_t imageHeaderAddr);
QWidget* initImageTable();
void initSymbolTable();
};
@@ -386,5 +223,4 @@ public:
static void Register();
};
-
#endif // BINARYNINJA_KCTRIAGE_H
diff --git a/view/kernelcache/ui/symboltable.cpp b/view/kernelcache/ui/symboltable.cpp
new file mode 100644
index 00000000..5c86b3b4
--- /dev/null
+++ b/view/kernelcache/ui/symboltable.cpp
@@ -0,0 +1,216 @@
+#include <progresstask.h>
+#include "symboltable.h"
+
+#include <QHeaderView>
+
+#include "ui/fontsettings.h"
+
+#include "binaryninjaapi.h"
+
+using namespace BinaryNinja;
+using namespace KernelCacheAPI;
+
+
+SymbolTableModel::SymbolTableModel(SymbolTableView* parent)
+ : QAbstractTableModel(parent), m_parent(parent) {
+ // TODO: Need to implement updating this font if it is changed by the user
+ m_font = getMonospaceFont(parent);
+}
+
+
+int SymbolTableModel::rowCount(const QModelIndex& parent) const {
+ Q_UNUSED(parent);
+ return static_cast<int>(m_modelSymbols.size());
+}
+
+
+int SymbolTableModel::columnCount(const QModelIndex& parent) const {
+ Q_UNUSED(parent);
+ // We have 3 columns: Address, Type, Name
+ return 3;
+}
+
+
+QVariant SymbolTableModel::data(const QModelIndex& index, int role) const {
+ if (!index.isValid() || (role != Qt::DisplayRole && role != Qt::FontRole)) {
+ return QVariant();
+ }
+
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ {
+ auto symbol = symbolAt(index.row());
+ auto symbolType = GetSymbolTypeAsString(symbol.type);
+
+ switch (index.column())
+ {
+ case 0: // Address column
+ return QString("0x%1").arg(symbol.address, 0, 16); // Display address as hexadecimal
+ case 1: // Type column
+ return QString::fromUtf8(symbolType.c_str(), symbolType.size());
+ case 2: // Name column
+ return QString::fromUtf8(symbol.name.c_str(), symbol.name.size());
+ default:
+ return QVariant();
+ }
+ }
+ case Qt::FontRole:
+ return m_font;
+ default:
+ return QVariant();
+ }
+}
+
+
+QVariant SymbolTableModel::headerData(int section, Qt::Orientation orientation, int role) const {
+ if (role != Qt::DisplayRole || orientation != Qt::Horizontal) {
+ return QVariant();
+ }
+
+ switch (section) {
+ case 0:
+ return QString("Address");
+ case 1:
+ return QString("Type");
+ case 2:
+ return QString("Name");
+ default:
+ return QVariant();
+ }
+}
+
+
+void SymbolTableModel::sort(int column, Qt::SortOrder order)
+{
+ beginResetModel();
+
+ std::function<bool(const CacheSymbol&, const CacheSymbol&)> comparator;
+
+ switch (column)
+ {
+ case 0: // Address column
+ comparator = [](const CacheSymbol& a, const CacheSymbol& b) {
+ return a.address < b.address;
+ };
+ break;
+ case 1: // Type column
+ comparator = [](const CacheSymbol& a, const CacheSymbol& b) {
+ return GetSymbolTypeAsString(a.type) < GetSymbolTypeAsString(b.type);
+ };
+ break;
+ case 2: // Name column
+ comparator = [](const CacheSymbol& a, const CacheSymbol& b) {
+ return a.name < b.name;
+ };
+ break;
+ default:
+ endResetModel();
+ return;
+ }
+
+ if (order == Qt::DescendingOrder)
+ {
+ std::sort(m_modelSymbols.begin(), m_modelSymbols.end(),
+ [&comparator](const CacheSymbol& a, const CacheSymbol& b) {
+ return comparator(b, a);
+ });
+ }
+ else
+ {
+ std::sort(m_modelSymbols.begin(), m_modelSymbols.end(), comparator);
+ }
+
+ endResetModel();
+}
+
+
+void SymbolTableModel::updateSymbols(std::vector<CacheSymbol>&& symbols)
+{
+ m_preparedSymbols = symbols;
+ setFilter(m_filter);
+}
+
+
+const CacheSymbol& SymbolTableModel::symbolAt(int row) const
+{
+ return m_modelSymbols.at(row);
+}
+
+
+void SymbolTableModel::setFilter(std::string text)
+{
+ beginResetModel();
+
+ m_filter = text;
+ m_modelSymbols = {};
+
+ // Skip filtering if no filter applied.
+ if (!m_filter.empty())
+ {
+ m_modelSymbols.reserve(m_preparedSymbols.size());
+ for (const auto& symbol : m_preparedSymbols)
+ if (((std::string_view)symbol.name).find(m_filter) != std::string::npos)
+ m_modelSymbols.push_back(symbol);
+ m_modelSymbols.shrink_to_fit();
+ }
+ else
+ {
+ m_modelSymbols = m_preparedSymbols;
+ }
+
+ endResetModel();
+}
+
+
+SymbolTableView::SymbolTableView(QWidget* parent)
+ : QTableView(parent), m_model(new SymbolTableModel(this)) {
+
+ // Set up the filter model
+ setModel(m_model);
+
+ // Configure view settings
+ horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed);
+ horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed);
+ horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
+ setEditTriggers(QAbstractItemView::NoEditTriggers);
+ setSelectionBehavior(QAbstractItemView::SelectRows);
+ setSelectionMode(QAbstractItemView::SingleSelection);
+ verticalHeader()->setVisible(false);
+
+ setSortingEnabled(true);
+}
+
+SymbolTableView::~SymbolTableView() {
+ delete m_model;
+}
+
+void SymbolTableView::populateSymbols(BinaryView &view)
+{
+ if (auto controller = KernelCacheController::GetController(view)) {
+ typedef std::vector<CacheSymbol> SymbolList;
+ // Retrieve the symbols from the controller in a future than pass that to the model.
+ QPointer<QFutureWatcher<SymbolList>> watcher = new QFutureWatcher<SymbolList>(this);
+ connect(watcher, &QFutureWatcher<SymbolList>::finished, this, [watcher, this]() {
+ if (watcher)
+ {
+ auto symbols = watcher->result();
+ m_model->updateSymbols(std::move(symbols));
+ }
+ });
+ QFuture<SymbolList> future = QtConcurrent::run([controller]() {
+ return controller->GetSymbols();
+ });
+ watcher->setFuture(future);
+ connect(this, &QObject::destroyed, this, [watcher]() {
+ if (watcher && watcher->isRunning()) {
+ watcher->cancel();
+ watcher->waitForFinished();
+ }
+ });
+ }
+}
+
+void SymbolTableView::setFilter(const std::string& filter) {
+ m_model->setFilter(filter);
+}
diff --git a/view/kernelcache/ui/symboltable.h b/view/kernelcache/ui/symboltable.h
new file mode 100644
index 00000000..866390c7
--- /dev/null
+++ b/view/kernelcache/ui/symboltable.h
@@ -0,0 +1,102 @@
+#pragma once
+
+#include <kernelcacheapi.h>
+#include "viewframe.h"
+
+#include <QTableView>
+#include <QStandardItemModel>
+#include "filter.h"
+
+#ifndef BINARYNINJA_KCSYMBOLTABLE_H
+#define BINARYNINJA_KCSYMBOLTABLE_H
+
+class SymbolTableView;
+
+
+class SymbolTableModel : public QAbstractTableModel
+{
+Q_OBJECT
+ SymbolTableView* m_parent;
+ QFont m_font;
+ std::string m_filter;
+ std::vector<KernelCacheAPI::CacheSymbol> m_preparedSymbols{};
+ // These are the symbols we actually use
+ std::vector<KernelCacheAPI::CacheSymbol> m_modelSymbols{};
+
+public:
+ explicit SymbolTableModel(SymbolTableView* parent);
+
+ 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 sort(int column, Qt::SortOrder order) override;
+ void updateSymbols(std::vector<KernelCacheAPI::CacheSymbol>&& symbols);
+ void setFilter(std::string text);
+ const KernelCacheAPI::CacheSymbol& symbolAt(int row) const;
+
+};
+
+
+class SymbolTableView : public QTableView, public FilterTarget
+{
+Q_OBJECT
+ friend class SymbolTableModel;
+
+ SymbolTableModel* m_model;
+
+public:
+ explicit SymbolTableView(QWidget* parent);
+ ~SymbolTableView() override;
+
+ // Call this to populate the symbols from the given view.
+ void populateSymbols(BinaryNinja::BinaryView& view);
+
+ void scrollToFirstItem() override
+ {
+ if (model()->rowCount() > 0) {
+ QModelIndex top = indexAt(rect().topLeft());
+ if (top.isValid())
+ scrollTo(top);
+ }
+ }
+
+ void scrollToCurrentItem() override
+ {
+ QModelIndex currentIndex = selectionModel()->currentIndex();
+ if (currentIndex.isValid())
+ scrollTo(currentIndex);
+ }
+
+ void selectFirstItem() override
+ {
+ if (model()->rowCount() > 0) {
+ QModelIndex top = indexAt(rect().topLeft());
+ if (top.isValid()) {
+ selectionModel()->select(top, QItemSelectionModel::ClearAndSelect);
+ setCurrentIndex(top);
+ }
+ }
+ }
+
+ void activateFirstItem() override
+ {
+ if (model()->rowCount() > 0) {
+ QModelIndex topLeft = indexAt(rect().topLeft());
+ if (topLeft.isValid()) {
+ setCurrentIndex(topLeft);
+ emit activated(topLeft);
+ }
+ }
+ }
+
+ KernelCacheAPI::CacheSymbol getSymbolAtRow(int row) const
+ {
+ return m_model->symbolAt(row);
+ }
+
+ void setFilter(const std::string& filter) override;
+};
+
+
+#endif // BINARYNINJA_KCSYMBOLTABLE_H