diff options
| author | kat <kat@vector35.com> | 2025-07-06 15:05:01 -0400 |
|---|---|---|
| committer | kat <kat@vector35.com> | 2025-07-07 07:37:23 -0400 |
| commit | 768f7c78465fb93936e5ca50a0ca712664fe54e7 (patch) | |
| tree | 78c73e0022d6006882e365a257595a5b55a37432 /view/kernelcache/ui | |
| parent | 8f3e251c42169fb4fe8db9403d900571434d88ff (diff) | |
KernelCache rewrite
Diffstat (limited to 'view/kernelcache/ui')
| -rw-r--r-- | view/kernelcache/ui/KernelCacheUINotifications.cpp | 133 | ||||
| -rw-r--r-- | view/kernelcache/ui/kctriage.cpp | 258 | ||||
| -rw-r--r-- | view/kernelcache/ui/kctriage.h | 198 | ||||
| -rw-r--r-- | view/kernelcache/ui/symboltable.cpp | 216 | ||||
| -rw-r--r-- | view/kernelcache/ui/symboltable.h | 102 |
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 ¤t, 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 |
