diff options
| author | Mason Reed <mason@vector35.com> | 2025-03-10 11:05:40 -0400 |
|---|---|---|
| committer | Mason Reed <mason@vector35.com> | 2025-04-02 05:36:54 -0400 |
| commit | 25cc02431b61097b2adfc2fbc493b648b0300c3b (patch) | |
| tree | a79d9c4f4f67234d3bf9bda413e8608f479a4cc8 /view/sharedcache/ui | |
| parent | fa85bf28502286c4821427c5d0ed91a7ed46f8f6 (diff) | |
[SharedCache] Refactor Shared Cache
In absence of a better name, this commit refactors the shared cache code.
Diffstat (limited to 'view/sharedcache/ui')
| -rw-r--r-- | view/sharedcache/ui/SharedCacheBDNotifications.cpp | 69 | ||||
| -rw-r--r-- | view/sharedcache/ui/SharedCacheBDNotifications.h | 20 | ||||
| -rw-r--r-- | view/sharedcache/ui/SharedCacheUINotifications.cpp | 229 | ||||
| -rw-r--r-- | view/sharedcache/ui/dscpicker.cpp | 69 | ||||
| -rw-r--r-- | view/sharedcache/ui/dsctriage.cpp | 1135 | ||||
| -rw-r--r-- | view/sharedcache/ui/dsctriage.h | 188 | ||||
| -rw-r--r-- | view/sharedcache/ui/symboltable.cpp | 129 | ||||
| -rw-r--r-- | view/sharedcache/ui/symboltable.h | 87 |
8 files changed, 690 insertions, 1236 deletions
diff --git a/view/sharedcache/ui/SharedCacheBDNotifications.cpp b/view/sharedcache/ui/SharedCacheBDNotifications.cpp deleted file mode 100644 index f59f313c..00000000 --- a/view/sharedcache/ui/SharedCacheBDNotifications.cpp +++ /dev/null @@ -1,69 +0,0 @@ -// -// Created by kat on 8/22/24. -// - -#include "SharedCacheBDNotifications.h" - - -SharedCacheBDNotifications::SharedCacheBDNotifications(Ref<BinaryView> view) - : BinaryDataNotification(FunctionUpdates | DataVariableUpdates) -{ -} - -void SharedCacheBDNotifications::OnAnalysisFunctionAdded(BinaryView* view, Function* func) -{ - // - // We just cannot do this until one of: - // "Component::AddAutoFunction" - // BinaryView::BeginIgnoredUndoActions - // some similar fix - - /* - if (view->GetTypeName() == VIEW_NAME) - { - auto sections = view->GetSectionsAt(func->GetStart()); - if (sections.size() > 0) - { - auto section = sections[0]; - auto imageName = section->GetName().substr(0, section->GetName().find("::")); - auto id = view->BeginUndoActions(); - auto comp = view->GetComponentByPath(imageName); - if (!comp) - { - comp = view->CreateComponentWithName(imageName); - } - comp.value()->AddFunction(func); - view->ForgetUndoActions(id); - } - } - */ -} - - -void SharedCacheBDNotifications::OnSectionAdded(BinaryView* data, Section* section) -{ - -} - - -void SharedCacheBDNotifications::OnDataVariableAdded(BinaryView* view, const DataVariable& var) -{ - /* - if (view->GetTypeName() == VIEW_NAME) - { - auto sections = view->GetSectionsAt(var.address); - if (sections.size() > 0) - { - auto section = sections[0]; - auto imageName = section->GetName().substr(0, section->GetName().find("::")); - auto comp = view->GetComponentByPath(imageName); - auto id = view->BeginUndoActions(); - if (!comp) - { - comp = view->CreateComponentWithName(imageName); - } - comp.value()->AddDataVariable(var); - view->ForgetUndoActions(id); - } - }*/ -} diff --git a/view/sharedcache/ui/SharedCacheBDNotifications.h b/view/sharedcache/ui/SharedCacheBDNotifications.h deleted file mode 100644 index 632fca28..00000000 --- a/view/sharedcache/ui/SharedCacheBDNotifications.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// Created by kat on 8/22/24. -// - -#pragma once - -#include <binaryninjaapi.h> -#include "ui/uicontext.h" -#include "SharedCacheUINotifications.h" - -using namespace BinaryNinja; - -class SharedCacheBDNotifications : public BinaryDataNotification -{ -public: - SharedCacheBDNotifications(Ref<BinaryView> view); - void OnAnalysisFunctionAdded(BinaryView* view, Function* func) override; - void OnDataVariableAdded(BinaryView* view, const DataVariable& var) override; - void OnSectionAdded(BinaryView* data, Section* section) override; -}; diff --git a/view/sharedcache/ui/SharedCacheUINotifications.cpp b/view/sharedcache/ui/SharedCacheUINotifications.cpp index 9d5893f9..a7b190ff 100644 --- a/view/sharedcache/ui/SharedCacheUINotifications.cpp +++ b/view/sharedcache/ui/SharedCacheUINotifications.cpp @@ -3,14 +3,15 @@ // #include "SharedCacheUINotifications.h" -#include <QLayout> #include <sharedcacheapi.h> #include "ui/sidebar.h" #include "ui/linearview.h" #include "ui/viewframe.h" #include "dscpicker.h" #include "progresstask.h" -#include "SharedCacheBDNotifications.h" + +using namespace BinaryNinja; +using namespace SharedCacheAPI; UINotifications* UINotifications::m_instance = nullptr; @@ -20,123 +21,141 @@ 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() == VIEW_NAME) + auto view = frame->getCurrentBinaryView(); + if (!view || view->GetTypeName() != 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 loadRegionAtAddr = [](BinaryView& view, uint64_t addr) { + auto controller = SharedCacheController::GetController(view); + if (!controller) + return; + if (auto foundRegion = controller->GetRegionContaining(addr)) { - for (const auto& seg : view->GetSegments()) - { - if (seg->GetStart() <= addr && seg->GetEnd() > addr) - return true; - } + // If we did not load the region, then we don't need to run analysis. + if (!controller->ApplyRegion(view, *foundRegion)) + return; + view.AddAnalysisOption("linearsweep"); + view.UpdateAnalysis(); } - return false; }; - auto view = frame->getCurrentBinaryView(); - if (view && view->GetTypeName() == VIEW_NAME) - { - if (auto viewInt = frame->getCurrentViewInterface()) + static auto loadImageAtAddr = [](BinaryView& view, uint64_t addr) { + auto controller = SharedCacheController::GetController(view); + if (!controller) + return; + if (auto foundImage = controller->GetImageContaining(addr)) { - auto ah = viewInt->actionHandler(); - if (!ah->isBoundAction("Load Image by Name")) - { - ah->bindAction("Load Image by Name", UIAction([view = view](const UIActionContext& ctx) { - DisplayDSCPicker(ctx.context, view); - })); - ah->bindAction("Load Section by Address", UIAction([view = view](const UIActionContext& ctx) { - Ref<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); - uint64_t addr = 0; - bool gotAddr = GetAddressInput(addr, "Address", "Address"); - if (gotAddr) - { - BackgroundThread::create(ctx.context->mainWindow())->thenBackground( - [cache=cache, addr=addr]() { - cache->LoadSectionAtAddress(addr); - })->start(); - } - })); - ah->bindAction("Load ADDRHERE", - UIAction( - [](const UIActionContext& ctx) { - Ref<BinaryView> view = ctx.binaryView; - Ref<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); - uint64_t addr = ctx.token.token.value; - if (addr) - { - BackgroundThread::create(ctx.context->mainWindow())->thenBackground( - [cache=cache, addr=addr]() { - cache->LoadSectionAtAddress(addr); - })->start(); - } - }, - [](const UIActionContext& ctx) { - Ref<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); - uint64_t addr = ctx.token.token.value; - if (isAddrMapped(ctx.binaryView, addr)) - return false; - return addr && cache->GetNameForAddress(addr) != ""; // bool - })); - ah->bindAction("Load IMGHERE", - UIAction( - [](const UIActionContext& ctx) { - Ref<BinaryView> view = ctx.binaryView; - Ref<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(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<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); - uint64_t addr = ctx.token.token.value; - if (isAddrMapped(ctx.binaryView, addr)) - return false; - return addr && cache->GetImageNameForAddress(addr) != ""; // bool - })); - ah->setActionDisplayName("Load ADDRHERE", [](const UIActionContext& ctx) { - Ref<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); - uint64_t addr = ctx.token.token.value; - if (addr) - return QString("Load ") + cache->GetNameForAddress(addr).c_str(); - return QString("Error"); - }); - ah->setActionDisplayName("Load IMGHERE", [](const UIActionContext& ctx) { - Ref<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(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("Load ADDRHERE", VIEW_NAME); - linearView->contextMenu().addAction("Load IMGHERE", VIEW_NAME); - linearView->contextMenu().addAction("Load Image by Name", "DSCView2"); - linearView->contextMenu().addAction("Load Section by Address", "DSCView2"); - linearView->contextMenu().setGroupOrdering(VIEW_NAME, 0); - linearView->contextMenu().setGroupOrdering("DSCView2", 1); - } - } + // 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(); } + }; + + auto loadImageNameAction = [](const UIActionContext& ctx) { + DisplayDSCPicker(ctx.context, ctx.binaryView); + }; + + auto loadSectionAddrAction = [](const UIActionContext& ctx) { + uint64_t addr = 0; + if (GetAddressInput(addr, "Address", "Address")) + { + BackgroundThread::create(ctx.context->mainWindow()) + ->thenBackground([ctx, addr](){ loadRegionAtAddr(*ctx.binaryView, addr); }) + ->start(); + } + }; + + auto loadRegionTokenAction = [](const UIActionContext& ctx) { + BackgroundThread::create(ctx.context->mainWindow()) + ->thenBackground([ctx](){ loadRegionAtAddr(*ctx.binaryView, ctx.token.token.value); }) + ->start(); + }; + + auto loadImageTokenAction = [](const UIActionContext& ctx) { + BackgroundThread::create(ctx.context->mainWindow()) + ->thenBackground([ctx](){ loadImageAtAddr(*ctx.binaryView, ctx.token.token.value); }) + ->start(); + }; + + auto isValidUnloadedRegionAction = [](const UIActionContext& ctx) { + uint64_t addr = ctx.token.token.value; + // Check if the region is already loaded in the view. + if (!ctx.binaryView->GetSectionsAt(addr).empty()) + return false; + auto controller = SharedCacheController::GetController(*ctx.binaryView); + if (!controller) + return false; + return controller->GetRegionContaining(addr).has_value(); + }; + + 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 = SharedCacheController::GetController(*ctx.binaryView); + if (!controller) + return false; + return controller->GetImageContaining(addr).has_value(); + }; + + ah->bindAction("Load Image by Name", UIAction(loadImageNameAction)); + ah->bindAction("Load Section by Address", UIAction(loadSectionAddrAction)); + + ah->bindAction("Load ADDRHERE", UIAction(loadRegionTokenAction, isValidUnloadedRegionAction)); + ah->bindAction("Load IMGHERE", UIAction(loadImageTokenAction, isValidUnloadedImageAction)); + + ah->setActionDisplayName("Load ADDRHERE", [](const UIActionContext& ctx) { + auto controller = SharedCacheController::GetController(*ctx.binaryView); + if (!controller) + return QString("NO CONTROLLER"); + uint64_t addr = ctx.token.token.value; + auto region = controller->GetRegionContaining(addr); + if (!region) + return QString("NO REGION"); + return QString("Load ") + region->name.c_str(); + }); + + ah->setActionDisplayName("Load IMGHERE", [](const UIActionContext& ctx) { + auto controller = SharedCacheController::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())) + { + linearView->contextMenu().addAction("Load ADDRHERE", VIEW_NAME); + linearView->contextMenu().addAction("Load IMGHERE", VIEW_NAME); + linearView->contextMenu().addAction("Load Image by Name", "DSCView2"); + linearView->contextMenu().addAction("Load Section by Address", "DSCView2"); + linearView->contextMenu().setGroupOrdering(VIEW_NAME, 0); + linearView->contextMenu().setGroupOrdering("DSCView2", 1); } } + void UINotifications::OnAfterOpenFile(UIContext* context, FileContext* file, ViewFrame* frame) { - if (frame->getCurrentBinaryView()) - { - auto listener = new SharedCacheBDNotifications(frame->getCurrentBinaryView()); - frame->getCurrentBinaryView()->RegisterNotification(listener); - } UIContextNotification::OnAfterOpenFile(context, file, frame); } diff --git a/view/sharedcache/ui/dscpicker.cpp b/view/sharedcache/ui/dscpicker.cpp index a82da4fc..c6448ff4 100644 --- a/view/sharedcache/ui/dscpicker.cpp +++ b/view/sharedcache/ui/dscpicker.cpp @@ -6,37 +6,56 @@ #include <sharedcacheapi.h> #include "progresstask.h" -#include <utility> - using namespace BinaryNinja; +using namespace SharedCacheAPI; void DisplayDSCPicker(UIContext* ctx, Ref<BinaryView> dscView) { - BackgroundThread::create(ctx ? ctx->mainWindow() : nullptr)->thenBackground( - [dscView=dscView](QVariant var) { - QStringList entries; - Ref<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(dscView); + static auto getImageNames = [dscView](QVariant var) { + auto controller = SharedCacheController::GetController(*dscView); + if (!controller) + return QStringList(); + + QStringList entries = {}; + for (const auto& img : controller->GetImages()) + entries.push_back(QString::fromStdString(img.name)); + return entries; + }; + + static auto getChosenImage = [ctx](QVariant var) { + QStringList entries = var.toStringList(); + + auto choiceDialog = new MetadataChoiceDialog(ctx ? ctx->mainWindow() : nullptr, "Pick Image", "Select", entries); + choiceDialog->AddWidthRequiredByItem(ctx, 300); + choiceDialog->AddHeightRequiredByItem(ctx, 150); + choiceDialog->exec(); + + if (!choiceDialog->GetChosenEntry().has_value()) + return QVariant(""); + + return QVariant(QString::fromStdString(entries.at((qsizetype)choiceDialog->GetChosenEntry().value().idx).toStdString())); + }; - for (const auto& img : cache->GetAvailableImages()) - entries.push_back(QString::fromStdString(img)); + static auto loadSelectedImage = [dscView](QVariant var) { + auto selectedImageName = var.toString().toStdString(); + if (selectedImageName.empty()) + return; - return entries; - })->thenMainThread([ctx](QVariant var){ - QStringList entries = var.toStringList(); + if (auto controller = SharedCacheController::GetController(*dscView)) + { + if (const auto selectedImage = controller->GetImageWithName( selectedImageName)) + { + controller->ApplyImage(*dscView, *selectedImage); + dscView->AddAnalysisOption("linearsweep"); + dscView->UpdateAnalysis(); + } + } + }; - auto choiceDialog = new MetadataChoiceDialog(ctx ? ctx->mainWindow() : nullptr, "Pick Image", "Select", entries); - choiceDialog->AddWidthRequiredByItem(ctx, 300); - choiceDialog->AddHeightRequiredByItem(ctx, 150); - choiceDialog->exec(); - if (choiceDialog->GetChosenEntry().has_value()) - return QVariant(QString::fromStdString(entries.at((qsizetype)choiceDialog->GetChosenEntry().value().idx).toStdString())); - else - return QVariant(""); - })->thenBackground([dscView=dscView](QVariant var){ - if (var.toString().isEmpty()) - return; - Ref<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(dscView); - cache->LoadImageWithInstallName(var.toString().toStdString()); - })->start(); + BackgroundThread::create(ctx ? ctx->mainWindow() : nullptr) + ->thenBackground([](QVariant var){ return getImageNames(var); }) + ->thenMainThread([](QVariant var){ return getChosenImage(var); }) + ->thenBackground([](QVariant var){ return loadSelectedImage(var); }) + ->start(); } diff --git a/view/sharedcache/ui/dsctriage.cpp b/view/sharedcache/ui/dsctriage.cpp index 41e7ed8b..aab71aac 100644 --- a/view/sharedcache/ui/dsctriage.cpp +++ b/view/sharedcache/ui/dsctriage.cpp @@ -2,698 +2,133 @@ // Created by kat on 8/15/24. // -#include "dsctriage.h" #include "ui/fontsettings.h" -#include <QPainter> -#include <QTextBrowser> #include "tabwidget.h" #include "globalarea.h" #include "progresstask.h" - -#include <cmath> #include <QMessageBox> +#include <QHeaderView> +#include "dsctriage.h" +#include <QTableWidgetItem> +#include "symboltable.h" +using namespace BinaryNinja; +using namespace SharedCacheAPI; -#define QSETTINGS_KEY_SELECTED_TAB "DSCTriage-SelectedTab" -#define QSETTINGS_KEY_TAB_LAYOUT "DSCTriage-TabLayout" -#define QSETTINGS_KEY_IMAGELOAD_TAB_LAYOUT "DSCTriage-ImageLoadTabLayout" -#define QSETTINGS_KEY_ALPHA_POPUP_SEEN "DSCTriage-AlphaPopupSeenV2" - - -DSCCacheBlocksView::DSCCacheBlocksView(QWidget* parent, BinaryViewRef data, Ref<SharedCacheAPI::SharedCache> cache) - : QWidget(parent), m_data(data), m_cache(cache) -{ - setMouseTracking(true); - m_backingCacheCount = SharedCacheAPI::SharedCache::FastGetBackingCacheCount(data); - if (m_backingCacheCount == 0) - return; - m_blockLuminance.resize(m_backingCacheCount, 128); - m_blockSizeRatios.resize(m_backingCacheCount, 1); - m_currentProgress = m_cache->GetLoadProgress(data); - m_targetBlockSizeForAnimation.resize(m_backingCacheCount, 0); - - m_blockWaveAnimation = Animation::create(this) - ->withDuration(1200) - ->withEasingCurve(QEasingCurve::Linear) - ->thenOnValueChanged([this](double v) - { - for (size_t i = 0; i < m_backingCacheCount; i++) - { - // Create a wave effect. - // We use sine to create the initial wave effect, and then cube it to make it more pronounced. - m_blockLuminance[i] = 128 + 95 * (pow((sin(v * 2 * M_PI + i * M_PI / m_backingCacheCount) + 1) / 2, 3)); - } - update(); - }) - ->thenOnEnd([this](QAbstractAnimation::Direction) - { - m_currentProgress = m_cache->GetLoadProgress(m_data); - if (m_currentProgress == BNDSCViewLoadProgress::LoadProgressFinished) - { - m_backingCaches = m_cache->GetBackingCaches(); - m_blockExpandAnimation->start(); - } - else - { - m_blockWaveAnimation->start(); - } - }); - m_blockExpandAnimation = Animation::create(this) - ->withDuration(600) - ->withEasingCurve(QEasingCurve::InOutCirc) - ->thenOnStart([this](QAbstractAnimation::Direction) - { - if (m_backingCaches.size() < m_backingCacheCount) - { - return; - } - uint64_t totalSize = 0; - uint64_t sumCountForAvg = 0; - for (size_t i = 0; i < m_backingCacheCount; i++) - { - const auto& backingCache = m_backingCaches[i]; - double sizeSum = 0.0; - - for (const auto& mapping : backingCache.mappings) - { - sizeSum += mapping.size; - } - m_targetBlockSizeForAnimation[i] = sizeSum; - totalSize += sizeSum; - sumCountForAvg++; - } - - uint64_t avgSize = totalSize / sumCountForAvg; - - for (size_t i = 0; i < m_backingCacheCount; i++) - { - m_blockSizeRatios[i] = avgSize; - } - - m_averageBlockSizeForAnimationInterp = avgSize; - }) - ->thenOnValueChanged([this](double v) - { - for (size_t i = 0; i < m_backingCacheCount; i++) - { - m_blockSizeRatios[i] = m_averageBlockSizeForAnimationInterp + (v/2) * (m_targetBlockSizeForAnimation[i] - ((1.0 - (v/2)) * m_averageBlockSizeForAnimationInterp)); - - // Adjust luminance based on animation progress - m_blockLuminance[i] = 128 + (63 * v); - } - update(); - }) - ->thenOnEnd([this](QAbstractAnimation::Direction) - { - std::fill(m_blockLuminance.begin(), m_blockLuminance.end(), 191); - update(); - // wait 300, somehow - emit loadDone(); - m_selectedBlock = 0; - m_blockAutoselectAnimation->start(); - }); - - m_blockAutoselectAnimation = Animation::create(this) - ->withDuration(100) - ->withEasingCurve(QEasingCurve::InOutCirc) - ->thenOnValueChanged([this](double v){ - m_blockLuminance[0] = 191 + (64 * v); - update(); - }) - ->thenOnEnd([this](QAbstractAnimation::Direction) - { - if (m_backingCaches.size() == 0) - return; - emit selectionChanged(m_backingCaches[0], true); - }); - - m_blockWaveAnimation->setDirection(QAbstractAnimation::Backward); - m_blockWaveAnimation->start(); - -} - -DSCCacheBlocksView::~DSCCacheBlocksView() -{ - -} - -void DSCCacheBlocksView::mousePressEvent(QMouseEvent* event) -{ - if (m_currentProgress != BNDSCViewLoadProgress::LoadProgressFinished - || m_selectedBlock == -1) - { - return; - } - int blockIndex = getBlockIndexAtPosition(event->pos()); - blockSelected(blockIndex); - QWidget::mousePressEvent(event); -} - - -void DSCCacheBlocksView::mouseReleaseEvent(QMouseEvent* event) -{ - QWidget::mouseReleaseEvent(event); -} - - -void DSCCacheBlocksView::mouseDoubleClickEvent(QMouseEvent* event) -{ - QWidget::mouseDoubleClickEvent(event); -} - - -void DSCCacheBlocksView::mouseMoveEvent(QMouseEvent* event) -{ - if (m_selectedBlock == -1) - { - return; - } - uint64_t hoveredIndex = getBlockIndexAtPosition(event->pos()); - std::fill(m_blockLuminance.begin(), m_blockLuminance.end(), 191); - if (hoveredIndex != -1) - { - m_blockLuminance[hoveredIndex] = 255 - 32; - } - m_blockLuminance[m_selectedBlock] = 255; - update(); -} - - -void DSCCacheBlocksView::keyPressEvent(QKeyEvent* event) -{ - QWidget::keyPressEvent(event); -} - - -void DSCCacheBlocksView::keyReleaseEvent(QKeyEvent* event) -{ - QWidget::keyReleaseEvent(event); - if (m_selectedBlock == -1) - { - return; - } - - // left/right arrows, inc/dec m_selectedBlock - if (event->key() == Qt::Key_Left) - { - if (m_selectedBlock > 0) - { - blockSelected(m_selectedBlock - 1); - } - } - else if (event->key() == Qt::Key_Right) - { - if (m_selectedBlock < m_backingCacheCount - 1) - { - blockSelected(m_selectedBlock + 1); - } - } -} - - -void DSCCacheBlocksView::focusInEvent(QFocusEvent* event) -{ - QWidget::focusInEvent(event); -} - - -void DSCCacheBlocksView::focusOutEvent(QFocusEvent* event) -{ - QWidget::focusOutEvent(event); -} - - -void DSCCacheBlocksView::enterEvent(QEnterEvent* event) -{ - QWidget::enterEvent(event); -} - - -void DSCCacheBlocksView::leaveEvent(QEvent* event) -{ - QWidget::leaveEvent(event); -} - -void DSCCacheBlocksView::paintEvent(QPaintEvent* event) -{ - QPainter painter(this); - painter.setRenderHint(QPainter::Antialiasing, true); - - // Initial X position and total width of the widget - int totalWidth = this->width(); - int totalHeight = 30; // Height of the rectangles - int totalSpacing = (m_blockSizeRatios.size() - 1) * 5; - int availableWidth = totalWidth - (50 * 2) - totalSpacing; // availableWidth minus the initial padding - - // Calculate the total ratio of block sizes - uint64_t totalRatio = 0; - for (const auto& ratio : m_blockSizeRatios) { - totalRatio += ratio; - } - - std::vector<int> originalWidths; - originalWidths.resize(m_blockSizeRatios.size(), (availableWidth / m_blockSizeRatios.size())); - - - // Calculate center points for each block - std::vector<int> centers; - centers.reserve(m_blockSizeRatios.size()); - int currentX = 50; - for (size_t i = 0; i < originalWidths.size(); ++i) { - centers.push_back(currentX + (originalWidths[i] / 2)); // Store the center point - currentX += originalWidths[i] + 5; // Update currentX for the next block - } - - // Now draw the blocks, adjusting the position to keep the center point constant - currentX = 50; - uint64_t lastBlockEnd = currentX - 5; - for (size_t i = 0; i < m_blockSizeRatios.size(); ++i) { - // Recalculate the width during animation - uint64_t adjustedAvailableWidth = availableWidth * m_blockSizeRatios[i]; - int blockWidth = std::max(10, static_cast<int>(adjustedAvailableWidth / totalRatio)); - - // Calculate the new X position to maintain the center - int newX = centers[i] - (blockWidth / 2); - if (newX > lastBlockEnd + 5) - { - int diff = newX - (lastBlockEnd + 5); - newX -= diff; - blockWidth += diff; - } - if (newX < lastBlockEnd + 5) - { - int diff = (lastBlockEnd + 5) - newX; - newX += diff; - blockWidth -= diff; - } - lastBlockEnd = newX + blockWidth; - - QRect blockRect(newX, (height() - totalHeight) / 2, blockWidth, totalHeight); - QColor blockColor(m_blockLuminance[i], m_blockLuminance[i], m_blockLuminance[i]); - painter.setBrush(blockColor); - painter.setPen(blockColor); - painter.drawRect(blockRect); - - currentX += blockWidth + 5; // Move to the next block's position - } -} - - -int DSCCacheBlocksView::getBlockIndexAtPosition(const QPoint& clickPosition) -{ - // Initial X position and total width of the widget - int totalWidth = this->width(); - int totalHeight = 50; // Height of the rectangles - int totalSpacing = (m_blockSizeRatios.size() - 1) * 5; - int availableWidth = totalWidth - (50 * 2) - totalSpacing; // availableWidth minus the initial padding - - // Calculate the total ratio of block sizes - uint64_t totalRatio = 0; - for (const auto& ratio : m_blockSizeRatios) - { - totalRatio += ratio; - } - - // Calculate center points for each block - std::vector<int> originalWidths; - originalWidths.resize(m_blockSizeRatios.size(), (availableWidth / m_blockSizeRatios.size())); - - std::vector<int> centers; - centers.reserve(m_blockSizeRatios.size()); - int currentX = 50; - for (size_t i = 0; i < originalWidths.size(); ++i) - { - centers.push_back(currentX + (originalWidths[i] / 2)); // Store the center point - currentX += originalWidths[i] + 5; // Update currentX for the next block - } - - // Now find the block that contains the click - currentX = 50; - uint64_t lastBlockEnd = currentX - 5; - for (size_t i = 0; i < m_blockSizeRatios.size(); ++i) - { - // Recalculate the width during animation - uint64_t adjustedAvailableWidth = availableWidth * m_blockSizeRatios[i]; - int blockWidth = std::max(10, static_cast<int>(adjustedAvailableWidth / totalRatio)); - - // Calculate the new X position to maintain the center - int newX = centers[i] - (blockWidth / 2); - if (newX > lastBlockEnd + 5) - { - int diff = newX - (lastBlockEnd + 5); - newX -= diff; - blockWidth += diff; - } - if (newX < lastBlockEnd + 5) - { - int diff = (lastBlockEnd + 5) - newX; - newX += diff; - blockWidth -= diff; - } - lastBlockEnd = newX + blockWidth; - - // Check if the clickPosition is inside the current block's rectangle - QRect blockRect(newX, (height() - totalHeight) / 2, blockWidth, totalHeight); - if (blockRect.contains(clickPosition)) - { - return static_cast<int>(i); // Return the index of the clicked block - } - - currentX += blockWidth + 5; // Move to the next block's position - } - - return -1; // Return -1 if no block was clicked -} - - -void DSCCacheBlocksView::blockSelected(int index) -{ - std::fill(m_blockLuminance.begin(), m_blockLuminance.end(), 191); - m_selectedBlock = index; - if (index != -1) - m_blockLuminance[index] = 255; - update(); - if (index != -1) - emit selectionChanged(m_backingCaches[index], false); -} - - -void DSCCacheBlocksView::resizeEvent(QResizeEvent* event) -{ - QWidget::resizeEvent(event); -} - +DSCTriageViewType::DSCTriageViewType() + : ViewType("DSCTriage", "Dyld Shared Cache Triage") +{} -QSize DSCCacheBlocksView::sizeHint() const +int DSCTriageViewType::getPriority(BinaryViewRef data, const QString& filename) { - return QWidget::sizeHint(); + if (data->GetTypeName() == VIEW_NAME) + return 100; + return 0; } - -QSize DSCCacheBlocksView::minimumSizeHint() const +QWidget* DSCTriageViewType::create(BinaryViewRef data, ViewFrame* viewFrame) { - return QWidget::minimumSizeHint(); -} - - -SymbolTableModel::SymbolTableModel(SymbolTableView* parent) - : QAbstractTableModel(parent), m_parent(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) { - return QVariant(); - } - - const SharedCacheAPI::DSCSymbol& 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::fromUtf8(symbol.name.c_str(), symbol.name.size()); - case 2: // Image column - return QString::fromStdString(symbol.image); - default: - return QVariant(); - } -} - -QVariant SymbolTableModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (role != Qt::DisplayRole || orientation != Qt::Horizontal) { - return QVariant(); - } - - switch (section) { - case 0: - return QString("Address"); - case 1: - return QString("Name"); - case 2: - return QString("Image"); - default: - return QVariant(); - } -} - -void SymbolTableModel::updateSymbols() { - m_symbols = m_parent->m_symbols; - setFilter(m_filter); -} - -const SharedCacheAPI::DSCSymbol& SymbolTableModel::symbolAt(int row) const { - if (row < 0 || row >= static_cast<int>(m_symbols.size())) { - return m_symbols.at(0); - } - return m_symbols.at(row); + if (data->GetTypeName() != VIEW_NAME) + return nullptr; + // TODO: Check for dyld start. Then continue. + return new DSCTriageView(viewFrame, data); } - -void SymbolTableModel::setFilter(std::string text) +void DSCTriageViewType::Register() { - beginResetModel(); - - m_filter = text; - m_symbols.clear(); - - if (m_filter.empty()) - { - m_symbols = m_parent->m_symbols; - } - else - { - m_symbols.reserve(m_parent->m_symbols.size()); - for (const auto& symbol : m_parent->m_symbols) - { - if (((std::string_view)symbol.name).find(m_filter) != std::string::npos) - { - m_symbols.push_back(symbol); - } - } - m_symbols.shrink_to_fit(); - } - - endResetModel(); -} - - -SymbolTableView::SymbolTableView(QWidget* parent, Ref<SharedCacheAPI::SharedCache> cache) - : m_model(new SymbolTableModel(this)) { - - // Set up the filter model - setModel(m_model); - - // Configure view settings - horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); - setSelectionBehavior(QAbstractItemView::SelectRows); - setSelectionMode(QAbstractItemView::SingleSelection); - - BackgroundThread::create(this)->thenBackground([this, cache=cache](){ - // LogInfo("Symbol Search: Loading symbols..."); - m_symbols = cache->LoadAllSymbolsAndWait(); - // LogInfo("Symbol Search: Loaded 0x%zx symbols", m_symbols.size()); - })->thenMainThread([this](){ - m_model->updateSymbols(); - })->start(); -} - -SymbolTableView::~SymbolTableView() { - delete m_model; -} - -void SymbolTableView::setFilter(const std::string& filter) { - m_model->setFilter(filter); + registerViewType(new DSCTriageViewType()); } - -DSCTriageView::DSCTriageView(QWidget* parent, BinaryViewRef data) : QWidget(parent), View(), m_data(data), m_cache(new SharedCacheAPI::SharedCache(data)) +DSCTriageView::DSCTriageView(QWidget* parent, BinaryViewRef data) : QWidget(parent), m_data(data) { setBinaryDataNavigable(false); setupView(this); + UIContext::registerNotification(this); + m_triageCollection = new DockableTabCollection(); m_triageTabs = new SplitTabWidget(m_triageCollection); auto triageTabStyle = new GlobalAreaTabStyle(); m_triageTabs->setTabStyle(triageTabStyle); - auto cacheInfoWidget = new QWidget; - auto cacheInfoLayout = new QVBoxLayout(cacheInfoWidget); - - QSplitter* containerWidget = new QSplitter; - containerWidget->setOrientation(Qt::Vertical); - - DSCCacheBlocksView* cacheBlocksView = new DSCCacheBlocksView(containerWidget, data, m_cache); - cacheBlocksView->setMinimumHeight(60); - - auto cacheInfo = new CollapsibleSection(this); - cacheInfo->setTitle(QString::fromStdString(data->GetFile()->GetOriginalFilename().substr(data->GetFile()->GetOriginalFilename().find_last_of('/') + 1))); - - auto cacheInfoSubwidget = new QWidget; - - auto mappingTable = new QTableView(cacheInfoSubwidget); - auto mappingModel = new QStandardItemModel(0, 3, mappingTable); - mappingModel->setHorizontalHeaderLabels({"VM Address", "File Address", "Size"}); - - mappingTable->setModel(mappingModel); - - mappingTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); - mappingTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); - mappingTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); - - mappingTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - - auto sectionTable = new QTableView(cacheInfoSubwidget); - auto sectionModel = new QStandardItemModel(0, 3, sectionTable); - sectionModel->setHorizontalHeaderLabels({"Name", "VM Address", "Size"}); - - sectionTable->setModel(sectionModel); - - sectionTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); - sectionTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); - sectionTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents); - - sectionTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - - auto mappingLabel = new QLabel("Mappings"); - auto sectionLabel = new QLabel("Sections"); - - auto mappingLayout = new QVBoxLayout; - mappingLayout->addWidget(mappingLabel); - mappingLayout->addWidget(mappingTable); - - auto sectionLayout = new QVBoxLayout; - sectionLayout->addWidget(sectionLabel); - sectionLayout->addWidget(sectionTable); - - cacheInfoLayout->addLayout(mappingLayout); - cacheInfoLayout->addLayout(sectionLayout); - - cacheInfo->setContentWidget(cacheInfoSubwidget); - - cacheInfo->setMinimumHeight(170); - - connect(cacheBlocksView, &DSCCacheBlocksView::selectionChanged, [this, sectionModel, cacheInfo, cacheInfoWidget, mappingModel](const SharedCacheAPI::BackingCache& index, bool _auto) - { - if (!_auto) - m_triageTabs->selectWidget(cacheInfoWidget); - mappingModel->removeRows(0, mappingModel->rowCount()); - sectionModel->removeRows(0, sectionModel->rowCount()); - auto basename = index.path.substr(index.path.find_last_of('/') + 1); - cacheInfo->setTitle(QString::fromStdString(basename)); - size_t sizeInBits = 0; - for (const auto& mapping : index.mappings) - { - sizeInBits += mapping.size; - mappingModel->appendRow({ - new QStandardItem(QString("0x%1").arg(mapping.vmAddress, 0, 16)), - new QStandardItem(QString("0x%1").arg(mapping.fileOffset, 0, 16)), - new QStandardItem(QString("0x%1").arg(mapping.size, 0, 16))}); - } + QWidget* defaultWidget = nullptr; - for (const auto& header : m_headers) - { - uint64_t i = 0; - for (const auto& section : header.sections) - { - for (const auto& mapping : index.mappings) - { - if (section.addr >= mapping.vmAddress && section.addr < mapping.vmAddress + mapping.size) - { - sectionModel->appendRow({ - new QStandardItem(QString::fromStdString(header.sectionNames[i])), - new QStandardItem(QString("0x%1").arg(section.addr, 0, 16)), - new QStandardItem(QString("0x%1").arg(section.size, 0, 16))}); - break; - } - } - i++; - } - continue; - } + static auto loadImagesWithAddr = [this](const std::vector<uint64_t>& addresses) { + auto controller = SharedCacheController::GetController(*this->m_data); + if (!controller) + return; - std::string sizeStr; - if (sizeInBits < 1024) - { - sizeStr = std::to_string(sizeInBits) + " B"; - } - else if (sizeInBits < 1024 * 1024) - { - sizeStr = std::to_string(sizeInBits / 1024) + " KB"; - } - else if (sizeInBits < 1024 * 1024 * 1024) + std::map<uint64_t, CacheImage> images = {}; + for (const uint64_t& addr : addresses) { - sizeStr = std::to_string(sizeInBits / (1024 * 1024)) + " MB"; - } - else - { - sizeStr = std::to_string(sizeInBits / (1024 * 1024 * 1024)) + " GB"; + 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}); } - cacheInfo->setSubtitleRight(QString::fromStdString(sizeStr)); - }); + // Don't create a worker action if we don't have any images. + if (images.empty()) + return; - containerWidget->addWidget(cacheInfo); + WorkerPriorityEnqueue([controller, this, images]() { + size_t loadedImages = 0; + const std::string initialLoad = fmt::format("Loading images... (0/{})", images.size()); + auto imageLoadTask = BackgroundTask(initialLoad, true); - QWidget* defaultWidget = nullptr; + 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(); - m_bottomRegionCollection = new DockableTabCollection(); - m_bottomRegionTabs = new SplitTabWidget(m_bottomRegionCollection); - m_bottomRegionTabs->setTabStyle(new GlobalAreaTabStyle()); + // We have loaded images, lets make sure to update analysis! + this->m_data->AddAnalysisOption("linearsweep"); + this->m_data->UpdateAnalysis(); + }); + }; + // Tab: Images auto loadImageTable = new FilterableTableView; { - auto loadImageModel = new QStandardItemModel(0, 2, loadImageTable); + m_imageModel = new QStandardItemModel(0, 3, loadImageTable); { - connect( - cacheBlocksView, &DSCCacheBlocksView::loadDone, [this, loadImageModel]() - { - for (const auto& img : m_cache->GetImages()) - { - if (auto header = m_cache->GetMachOHeaderForAddress(img.headerAddress); header) - { - m_headers.push_back(*header); - } - loadImageModel->appendRow({ - new QStandardItem(QString::fromStdString(img.name)), - new QStandardItem(QString("0x%1").arg(img.headerAddress, 0, 16))}); - } - }); - loadImageModel->setHorizontalHeaderLabels({"Name", "VM Address"}); + m_imageModel->setHorizontalHeaderLabels({"Address", "Loaded", "Name"}); } // loadImageModel auto loadImageButton = new CustomStyleFlatPushButton(); { connect(loadImageButton, &QPushButton::clicked, - [this, loadImageTable](bool) { + [loadImageTable](bool) { auto selected = loadImageTable->selectionModel()->selectedRows(); - if (selected.size() == 0) - { - return; - } - - auto name = selected[0].data().toString().toStdString(); - WorkerPriorityEnqueue([this, name]() { m_cache->LoadImageWithInstallName(name); }); + std::vector<uint64_t> addresses; + for (const auto& row : selected) + addresses.push_back(row.data().toString().toULongLong(nullptr, 16)); + loadImagesWithAddr(addresses); }); - loadImageButton->setText("Load"); + loadImageButton->setText("Load Selected"); loadImageButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); loadImageButton->setMinimumWidth(100); loadImageButton->setMinimumHeight(30); - } // loadImageButton - loadImageTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + + auto refreshDataButton = new CustomStyleFlatPushButton(); + { + // 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->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + refreshDataButton->setMinimumWidth(100); + refreshDataButton->setMinimumHeight(30); + } // refreshDataButton auto loadImageFilterEdit = new FilterEdit(loadImageTable); { @@ -704,317 +139,325 @@ DSCTriageView::DSCTriageView(QWidget* parent, BinaryViewRef data) : QWidget(pare connect(loadImageTable, &FilterableTableView::activated, this, [=](const QModelIndex& index) { - auto name = loadImageModel->item(index.row(), 0)->text().toStdString(); - WorkerPriorityEnqueue([this, name]() - { - m_cache->LoadImageWithInstallName(name); - }); + auto addr = m_imageModel->item(index.row(), 0)->text().toULongLong(nullptr, 16); + loadImagesWithAddr({addr}); }); + auto loadImageLayout = new QVBoxLayout; loadImageLayout->addWidget(loadImageFilterEdit); loadImageLayout->addWidget(loadImageTable); - loadImageLayout->addWidget(loadImageButton); + auto buttonLayout = new QHBoxLayout; + buttonLayout->addWidget(loadImageButton); + buttonLayout->addWidget(refreshDataButton); + buttonLayout->setAlignment(Qt::AlignLeft); + loadImageLayout->addLayout(buttonLayout); auto loadImageWidget = new QWidget; loadImageWidget->setLayout(loadImageLayout); - m_bottomRegionTabs->addTab(loadImageWidget, "Load an Image"); + loadImageTable->setModel(m_imageModel); - loadImageTable->setModel(loadImageModel); + loadImageTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - loadImageTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + loadImageTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); loadImageTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + loadImageTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); loadImageTable->setSelectionBehavior(QAbstractItemView::SelectRows); - loadImageTable->setSelectionMode(QAbstractItemView::SingleSelection); + loadImageTable->setSelectionMode(QAbstractItemView::ExtendedSelection); + + loadImageTable->setSortingEnabled(true); + + loadImageTable->verticalHeader()->setVisible(false); m_triageTabs->addTab(loadImageWidget, "Images"); defaultWidget = loadImageWidget; m_triageTabs->setCanCloseTab(loadImageWidget, false); } // loadImageTable - auto symbolSearch = new SymbolTableView(this, m_cache); + // Tab: Symbols + m_symbolTable = new SymbolTableView(this); { - auto symbolFilterEdit = new FilterEdit(symbolSearch); + auto symbolFilterEdit = new FilterEdit(m_symbolTable); { - connect(symbolFilterEdit, &FilterEdit::textChanged, [symbolSearch](const QString& filter) { - symbolSearch->setFilter(filter.toStdString()); + connect(symbolFilterEdit, &FilterEdit::textChanged, [this](const QString& filter) { + m_symbolTable->setFilter(filter.toStdString()); }); } + auto loadSymbolImageButton = new CustomStyleFlatPushButton(); + { + 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"); + + loadSymbolImageButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + loadSymbolImageButton->setMinimumWidth(100); + loadSymbolImageButton->setMinimumHeight(30); + } // loadImageButton + + // Shows the current selected rows image name. + auto currentImageLabel = new QLabel(this); { + currentImageLabel->setText(""); + currentImageLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + } + + // Update the label whenever the selection changes. + connect(m_symbolTable->selectionModel(), &QItemSelectionModel::currentRowChanged, this, + [this, currentImageLabel](const QModelIndex ¤t, const QModelIndex &) { + auto symbol = m_symbolTable->getSymbolAtRow(current.row()); + auto controller = SharedCacheController::GetController(*this->m_data); + if (!controller) + return; + auto image = controller->GetImageContaining(symbol.address); + if (image) + currentImageLabel->setText("Image: " + QString::fromStdString(image->name)); + else + currentImageLabel->setText(""); + }); + + auto symbolFooterLayout = new QHBoxLayout; + symbolFooterLayout->addWidget(loadSymbolImageButton); + symbolFooterLayout->addWidget(currentImageLabel); + + symbolFooterLayout->setAlignment(Qt::AlignLeft); + auto symbolLayout = new QVBoxLayout; symbolLayout->addWidget(symbolFilterEdit); - symbolLayout->addWidget(symbolSearch); + symbolLayout->addWidget(m_symbolTable); + symbolLayout->addLayout(symbolFooterLayout); 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 + m_symbolTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); // Address + m_symbolTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); // Name - symbolSearch->setSelectionBehavior(QAbstractItemView::SelectRows); - symbolSearch->setSelectionMode(QAbstractItemView::SingleSelection); + m_symbolTable->setSelectionBehavior(QAbstractItemView::SelectRows); + m_symbolTable->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:DSCView", addr); - else - m_data->Navigate("Linear:DSCView", addr); - }); - }; + connect(m_symbolTable, &SymbolTableView::activated, this, [=](const QModelIndex& index){ + auto symbol = m_symbolTable->getSymbolAtRow(index.row()); + auto dialog = new QMessageBox(this); - connect(symbolSearch, &SymbolTableView::activated, this, [=](const QModelIndex& index) - { - auto symbol = symbolSearch->getSymbolAtRow(index.row()); - WorkerPriorityEnqueue([this, symbol, navigateToAddress]() + auto controller = SharedCacheController::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) { - if (m_data->IsValidOffset(symbol.address)) - navigateToAddress(symbol.address); - else - { - m_cache->LoadImageWithInstallName(symbol.image); - navigateToAddress(symbol.address); - } + if (button == dialog->button(QMessageBox::Yes)) + loadImagesWithAddr({image->headerAddress}); }); + + dialog->exec(); }); - m_triageTabs->addTab(symbolWidget, "Symbol Search"); + m_triageTabs->addTab(symbolWidget, "Symbols"); m_triageTabs->setCanCloseTab(symbolWidget, false); } // symbolSearch - auto loadedRegions = new QTreeView; + // Tab: Mappings & Regions + auto cacheInfoWidget = new QWidget; { - auto loadedRegionsModel = new QStandardItemModel(0, 3, loadedRegions); - loadedRegionsModel->setHorizontalHeaderLabels({"VM Address", "Size", "Pretty Name"}); + auto cacheInfoLayout = new QVBoxLayout(cacheInfoWidget); - auto loadedRegionsLayout = new QVBoxLayout; - loadedRegionsLayout->addWidget(loadedRegions); + auto cacheInfoSubwidget = new QWidget; - auto loadedRegionsWidget = new QWidget; - loadedRegionsWidget->setLayout(loadedRegionsLayout); + auto mappingTable = new QTableView(cacheInfoSubwidget); + m_mappingModel = new QStandardItemModel(0, 4, mappingTable); + m_mappingModel->setHorizontalHeaderLabels({"Address", "Size", "File Address", "File Path"}); - loadedRegions->setModel(loadedRegionsModel); + mappingTable->setModel(m_mappingModel); - loadedRegions->header()->setSectionResizeMode(QHeaderView::Stretch); + mappingTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + mappingTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + mappingTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents); + mappingTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); - loadedRegions->setSelectionBehavior(QAbstractItemView::SelectRows); - loadedRegions->setSelectionMode(QAbstractItemView::SingleSelection); + mappingTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - connect(loadedRegions, &QTreeView::doubleClicked, this, [=](const QModelIndex& index) - { - auto addr = loadedRegionsModel->item(index.row(), 0)->text().toULongLong(nullptr, 16); - }); + mappingTable->setSelectionBehavior(QAbstractItemView::SelectRows); + mappingTable->setSelectionMode(QAbstractItemView::ExtendedSelection); - connect(loadedRegions, &QTreeView::activated, this, [=](const QModelIndex& index) - { - auto addr = loadedRegionsModel->item(index.row(), 0)->text().toULongLong(nullptr, 16); - }); + mappingTable->setSortingEnabled(true); - // m_triageTabs->addTab(loadedRegionsWidget, "Loaded Regions"); - } // loadedRegions + mappingTable->verticalHeader()->setVisible(false); - m_triageTabs->addTab(cacheInfoWidget, "Cache Info"); - m_triageTabs->setCanCloseTab(cacheInfoWidget, false); + auto regionTable = new FilterableTableView(cacheInfoSubwidget); + m_regionModel = new QStandardItemModel(0, 4, regionTable); + m_regionModel->setHorizontalHeaderLabels({"Address", "Size", "Type", "Name"}); - // check for alpha popup qsetting - QSettings settings; + regionTable->setModel(m_regionModel); - QTextBrowser *tb = new QTextBrowser(this); - { - tb->setOpenExternalLinks(true); - auto alphaHtml = - R"( -<h1>Dyld Shared Cache Alpha</h1> + regionTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + regionTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + regionTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Interactive); + regionTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); -<p> This is an experimental alpha release of the dyld shared cache view! We are hard at work improving this and adding features, but we wanted -to make it available for users to experiment with as soon as possible. </p> + regionTable->setEditTriggers(QAbstractItemView::NoEditTriggers); -<h2> Platforms </h2> -<dl> - <dt> iOS 11-17 </dt><dd> Full Support </dd> - <dt> iOS 18 </dt><dd> Partial support, may experience issues -- specifically, Objective-C optimization parsing is not implemented </dd> - <dt> macOS x86/arm64e </dt><dd> Partial support, may experience issues </dd> -</dl> + regionTable->setSelectionBehavior(QAbstractItemView::SelectRows); + regionTable->setSelectionMode(QAbstractItemView::ExtendedSelection); -<p> iOS parsing should work fairly well for now. macOS parsing should be usable, but both are still a work in progress. </p> -<h2> Getting the latest version of the plugin </h2> -<p> We frequently release "dev" builds which will contain the latest version of the dyld shared cache 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> + regionTable->setSortingEnabled(true); -<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/sharedcache">here</a>. </p> -<p> Contributions are always welcome! </p> -)"; - tb->setHtml(alphaHtml); + regionTable->verticalHeader()->setVisible(false); - m_triageTabs->addTab(tb, "Dyld Shared Cache Alpha"); - m_triageTabs->setCanCloseTab(tb, false); + auto mappingLabel = new QLabel("Mappings"); - } - if (!(settings.contains(QSETTINGS_KEY_ALPHA_POPUP_SEEN) && settings.value(QSETTINGS_KEY_ALPHA_POPUP_SEEN).toBool())) - { - LogAlert("dyld_shared_cache support is highly experimental! We do not expect it to work on all versions yet! See the 'Dlyd Shared Cache Alpha' tab in the DSCTriage view for more information. "); - settings.setValue(QSETTINGS_KEY_ALPHA_POPUP_SEEN, true); - defaultWidget = tb; - } + auto regionLabel = new QLabel("Regions"); + auto regionFilterEdit = new FilterEdit(regionTable); + { + connect(regionFilterEdit, &FilterEdit::textChanged, [regionTable](const QString& filter) { + regionTable->setFilter(filter.toStdString()); + }); + } + + auto regionHeaderLayout = new QHBoxLayout; + regionHeaderLayout->addWidget(regionLabel); + regionHeaderLayout->addWidget(regionFilterEdit); + regionHeaderLayout->setAlignment(Qt::AlignJustify); + regionHeaderLayout->setSpacing(30); + + auto mappingLayout = new QVBoxLayout; + mappingLayout->addWidget(mappingLabel); + mappingLayout->addWidget(mappingTable); + + auto regionLayout = new QVBoxLayout; + regionLayout->addLayout(regionHeaderLayout); + regionLayout->addWidget(regionTable); + + cacheInfoLayout->addLayout(mappingLayout); + cacheInfoLayout->addLayout(regionLayout); - containerWidget->addWidget(m_bottomRegionTabs); + m_triageTabs->addTab(cacheInfoWidget, "Mappings & Regions"); + m_triageTabs->setCanCloseTab(cacheInfoWidget, false); + } // cacheInfoSection m_layout = new QVBoxLayout(this); - m_layout->addWidget(cacheBlocksView); 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); +} QFont DSCTriageView::getFont() { return getMonospaceFont(this); } - BinaryViewRef DSCTriageView::getData() { return m_data; } - bool DSCTriageView::navigate(uint64_t offset) { - return false; + // TODO: We have to set this to true otherwise view restore does not pickup this view. + return true; } - uint64_t DSCTriageView::getCurrentOffset() { return 0; } - -CollapsibleSection::CollapsibleSection(QWidget* parent) - : QWidget(parent) +void DSCTriageView::OnAfterOpenFile(UIContext *context, FileContext *file, ViewFrame *frame) { - auto layout = new QVBoxLayout(this); - { - layout->setContentsMargins(0, 0, 0, 0); - - auto hLayout = new QHBoxLayout; - { - hLayout->setContentsMargins(0, 0, 0, 0); - - m_titleLabel = new QLabel; - m_titleLabel->setStyleSheet("font-weight: bold; font-size: 16px;"); - hLayout->addWidget(m_titleLabel, 1); - - m_subtitleRightLabel = new QLabel; - m_subtitleRightLabel->setStyleSheet("font-size: 12px;"); - hLayout->addWidget(m_subtitleRightLabel); - - m_collapseButton = new CustomStyleFlatPushButton; - m_collapseButton->setFlat(true); - m_collapseButton->setCheckable(true); - } - - layout->addLayout(hLayout); - } - - m_contentWidgetContainer = new QWidget; - { - layout->addWidget(m_contentWidgetContainer); - new QVBoxLayout(m_contentWidgetContainer); - } - -} - - -void CollapsibleSection::setTitle(const QString& title) -{ - m_titleLabel->setText(title); + RefreshData(); + UIContextNotification::OnAfterOpenFile(context, file, frame); } - -void CollapsibleSection::setSubtitleRight(const QString& subtitle) -{ - m_subtitleRightLabel->setVisible(subtitle != ""); - m_subtitleRightLabel->setText(subtitle); -} - - -void CollapsibleSection::setContentWidget(QWidget* contentWidget) +// Called when shared cache information has changed. +void DSCTriageView::RefreshData() { - m_contentWidget = contentWidget; - m_contentWidgetContainer->layout()->addWidget(contentWidget); -} - - -QSize CollapsibleSection::sizeHint() const -{ - return QWidget::sizeHint(); -} - + // Controller should be available after view init. + auto controller = SharedCacheController::GetController(*m_data); + if (!controller) + return; -void CollapsibleSection::setCollapsed(bool collapsed, bool animated) -{ - if (collapsed == m_collapsed) + m_imageModel->setRowCount(0); + for (const auto& img : controller->GetImages()) { - return; + m_imageModel->appendRow({ + new QStandardItem(QString("0x%1").arg(img.headerAddress, 0, 16)), + new QStandardItem(""), + new QStandardItem(QString::fromStdString(img.name)) + }); } - m_collapsed = collapsed; + // Set images as loaded (updating the relevant image row) + for (const auto& loadedImg : controller->GetLoadedImages()) + setImageLoaded(loadedImg.headerAddress); - if (m_collapsed) + m_regionModel->setRowCount(0); + for (const auto& region : controller->GetRegions()) { - m_contentWidget->hide(); - } - else - { - m_contentWidget->show(); + m_regionModel->appendRow({ + new QStandardItem(QString("0x%1").arg(region.start, 0, 16)), + new QStandardItem(QString("0x%1").arg(region.size, 0, 16)), + new QStandardItem(QString::fromStdString(GetRegionTypeAsString(region.type))), + new QStandardItem(QString::fromStdString(region.name)) + }); } - if (animated) + m_mappingModel->setRowCount(0); + for (const auto& entry : controller->GetEntries()) { - m_onContentAddedAnimation->start(); + for (const auto& mapping : entry.mappings) + { + m_mappingModel->appendRow({ + new QStandardItem(QString("0x%1").arg(mapping.vmAddress, 0, 16)), + new QStandardItem(QString("0x%1").arg(mapping.size, 0, 16)), + new QStandardItem(QString("0x%1").arg(mapping.fileOffset, 0, 16)), + new QStandardItem(QString::fromStdString(entry.path)) + }); + } } -} - - -DSCTriageViewType::DSCTriageViewType() - : ViewType("DSCTriage", "Dyld Shared Cache Triage") -{ + m_symbolTable->populateSymbols(*m_data); } - -int DSCTriageViewType::getPriority(BinaryViewRef data, const QString& filename) +void DSCTriageView::setImageLoaded(const uint64_t imageHeaderAddr) { - if (data->GetTypeName() == VIEW_NAME) - { - return 100; - } - return 0; -} - + // TODO: Better icon... probably something like ✅ + static QIcon loadedIcon(":/icons/images/plus.png"); -QWidget* DSCTriageViewType::create(BinaryViewRef data, ViewFrame* viewFrame) -{ - if (data->GetTypeName() != VIEW_NAME) + // Go through the m_loadImageModel and find the image associated with the address + // then set the image as loaded. + for (int i = 0; i < m_imageModel->rowCount(); i++) { - return nullptr; - } - if (SharedCacheAPI::SharedCache::FastGetBackingCacheCount(data) == 0) - { - return nullptr; + auto addrCol = m_imageModel->index(i, 0); + const auto addr = addrCol.data().toString().toULongLong(nullptr, 16); + if (addr == imageHeaderAddr) + { + auto statusCol = m_imageModel->index(i, 1); + m_imageModel->setData(statusCol, QString("•"), Qt::DisplayRole); + break; + } } - return new DSCTriageView(viewFrame, data); -} - - -void DSCTriageViewType::Register() -{ - ViewType::registerViewType(new DSCTriageViewType()); } diff --git a/view/sharedcache/ui/dsctriage.h b/view/sharedcache/ui/dsctriage.h index 39f06ce7..8c4ceb3f 100644 --- a/view/sharedcache/ui/dsctriage.h +++ b/view/sharedcache/ui/dsctriage.h @@ -2,118 +2,32 @@ // Created by kat on 8/15/24. // -#include <sharedcacheapi.h> #include <binaryninjaapi.h> +#include <QStyledItemDelegate> + #include "uitypes.h" #include "viewframe.h" #include "animation.h" #include "uicontext.h" -#include <QTableView> -#include <QStandardItemModel> -#include <QSortFilterProxyModel> -#include <QHeaderView> #include "filter.h" +#include "symboltable.h" #ifndef BINARYNINJA_DSCTRIAGE_H #define BINARYNINJA_DSCTRIAGE_H - -class DSCCacheBlocksView : public QWidget -{ - Q_OBJECT - - BinaryViewRef m_data; - Ref<SharedCacheAPI::SharedCache> m_cache; - - uint64_t m_backingCacheCount = 0; - std::vector<SharedCacheAPI::BackingCache> m_backingCaches; - - std::atomic<BNDSCViewLoadProgress> m_currentProgress; - std::vector<uint64_t> m_blockSizeRatios; - std::vector<uint64_t> m_targetBlockSizeForAnimation; - uint64_t m_averageBlockSizeForAnimationInterp = 0; - std::vector<uint64_t> m_blockLuminance; - Animation* m_blockWaveAnimation; - Animation* m_blockExpandAnimation; - Animation* m_blockAutoselectAnimation; - - int m_selectedBlock = -1; - - int getBlockIndexAtPosition(const QPoint& clickPosition); - - void blockSelected(int index); - -public: - DSCCacheBlocksView(QWidget* parent, BinaryViewRef data, Ref<SharedCacheAPI::SharedCache> cache); - virtual ~DSCCacheBlocksView() override; - -protected: - void mousePressEvent(QMouseEvent* event) override; - void mouseReleaseEvent(QMouseEvent* event) override; - void mouseDoubleClickEvent(QMouseEvent* event) override; - void mouseMoveEvent(QMouseEvent* event) override; - void keyPressEvent(QKeyEvent* event) override; - void keyReleaseEvent(QKeyEvent* event) override; - void focusInEvent(QFocusEvent* event) override; - void focusOutEvent(QFocusEvent* event) override; - void enterEvent(QEnterEvent* event) override; - void leaveEvent(QEvent* event) override; - void paintEvent(QPaintEvent* event) override; - void resizeEvent(QResizeEvent* event) override; - -public: - QSize sizeHint() const override; - QSize minimumSizeHint() const override; - -signals: - void loadDone(); - void selectionChanged(const SharedCacheAPI::BackingCache& index, bool automatic); -}; - - -class CollapsibleSection : public QWidget -{ - 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); - - void setContentWidget(QWidget* contentWidget); - - void setCollapsed(bool collapsed, bool animated = true); - bool isCollapsed() const { return m_collapsed; } -}; - - class FilterableTableView : public QTableView, public FilterTarget { Q_OBJECT 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); } - ~FilterableTableView() override {} + ~FilterableTableView() override = default; void setFilter(const std::string& filter) override { if (!m_filterByHiding) @@ -183,102 +97,34 @@ signals: void filterTextChanged(const QString& text); }; -class SymbolTableView; - -class SymbolTableModel : public QAbstractTableModel { - Q_OBJECT - - SymbolTableView* m_parent; - std::string m_filter; - std::vector<SharedCacheAPI::DSCSymbol> 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; - - void updateSymbols(); - - void setFilter(std::string text); - - const SharedCacheAPI::DSCSymbol& symbolAt(int row) const; -}; - - -class SymbolTableView : public QTableView, public FilterTarget -{ - Q_OBJECT - friend class SymbolTableModel; - - std::vector<SharedCacheAPI::DSCSymbol> m_symbols; - - SymbolTableModel* m_model; - -public: - SymbolTableView(QWidget* parent, Ref<SharedCacheAPI::SharedCache> cache); - virtual ~SymbolTableView() override; - - void scrollToFirstItem() override { - if (model()->rowCount() > 0) { - scrollTo(model()->index(0, 0)); - } - } - - void scrollToCurrentItem() override { - QModelIndex currentIndex = selectionModel()->currentIndex(); - if (currentIndex.isValid()) { - scrollTo(currentIndex); - } - } - - void selectFirstItem() override { - if (model()->rowCount() > 0) { - QModelIndex firstIndex = model()->index(0, 0); - selectionModel()->select(firstIndex, QItemSelectionModel::ClearAndSelect); - } - } - - void activateFirstItem() override { - if (model()->rowCount() > 0) { - QModelIndex firstIndex = model()->index(0, 0); - setCurrentIndex(firstIndex); - emit activated(firstIndex); - } - } - - SharedCacheAPI::DSCSymbol getSymbolAtRow(int row) const - { - return m_model->symbolAt(row); - } - - void setFilter(const std::string& filter) override; -}; - - -class DSCTriageView : public QWidget, public View +class DSCTriageView : public QWidget, public View, public UIContextNotification { BinaryViewRef m_data; QVBoxLayout* m_layout; - Ref<SharedCacheAPI::SharedCache> m_cache; SplitTabWidget* m_triageTabs; DockableTabCollection* m_triageCollection; - SplitTabWidget* m_bottomRegionTabs; - DockableTabCollection* m_bottomRegionCollection; + QStandardItemModel* m_imageModel; + + SymbolTableView* m_symbolTable; - std::vector<SharedCacheAPI::SharedCacheMachOHeader> m_headers; + QStandardItemModel* m_mappingModel; + + QStandardItemModel* m_regionModel; public: DSCTriageView(QWidget* parent, BinaryViewRef data); + ~DSCTriageView() override; BinaryViewRef getData() override; void setSelectionOffsets(BNAddressRange range) override {}; QFont getFont() override; bool navigate(uint64_t offset) override; uint64_t getCurrentOffset() override; + + void OnAfterOpenFile(UIContext* context, FileContext* file, ViewFrame* frame) override; + void RefreshData(); + void setImageLoaded(uint64_t imageHeaderAddr); }; diff --git a/view/sharedcache/ui/symboltable.cpp b/view/sharedcache/ui/symboltable.cpp new file mode 100644 index 00000000..c5e2d7bf --- /dev/null +++ b/view/sharedcache/ui/symboltable.cpp @@ -0,0 +1,129 @@ +#include <progresstask.h> +#include "symboltable.h" + +#include <QHeaderView> + +#include "binaryninjaapi.h" + +using namespace BinaryNinja; +using namespace SharedCacheAPI; + +SymbolTableModel::SymbolTableModel(SymbolTableView* parent) + : QAbstractTableModel(parent), m_parent(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 2 columns: Address, Name + return 2; +} + +QVariant SymbolTableModel::data(const QModelIndex& index, int role) const { + if (!index.isValid() || role != Qt::DisplayRole) { + return QVariant(); + } + + const CacheSymbol& 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::fromUtf8(symbol.name.c_str(), symbol.name.size()); + default: + return QVariant(); + } +} + +QVariant SymbolTableModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (role != Qt::DisplayRole || orientation != Qt::Horizontal) { + return QVariant(); + } + + switch (section) { + case 0: + return QString("Address"); + case 1: + return QString("Name"); + default: + return QVariant(); + } +} + +void SymbolTableModel::updateSymbols() { + m_symbols = m_parent->m_symbols; + setFilter(m_filter); +} + +const CacheSymbol& SymbolTableModel::symbolAt(int row) const { + return m_symbols.at(row); +} + + +void SymbolTableModel::setFilter(std::string text) +{ + beginResetModel(); + + m_filter = text; + m_symbols.clear(); + + if (m_filter.empty()) + { + m_symbols = m_parent->m_symbols; + } + else + { + m_symbols.reserve(m_parent->m_symbols.size()); + for (const auto& symbol : m_parent->m_symbols) + { + if (((std::string_view)symbol.name).find(m_filter) != std::string::npos) + { + m_symbols.push_back(symbol); + } + } + m_symbols.shrink_to_fit(); + } + + endResetModel(); +} + + +SymbolTableView::SymbolTableView(QWidget* parent) + : m_model(new SymbolTableModel(this)) { + + // Set up the filter model + setModel(m_model); + + // Configure view settings + horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + setSelectionBehavior(QAbstractItemView::SelectRows); + setSelectionMode(QAbstractItemView::SingleSelection); + + setSortingEnabled(true); +} + +SymbolTableView::~SymbolTableView() { + delete m_model; +} + +void SymbolTableView::populateSymbols(BinaryView &view) +{ + if (auto controller = SharedCacheController::GetController(view)) { + BackgroundThread::create(this) + ->thenBackground([this, controller]() { + std::vector<CacheSymbol> newSymbols = controller->GetSymbols(); + m_symbols.swap(newSymbols); + }) + ->thenMainThread([this](){ m_model->updateSymbols(); }) + ->start(); + } +} + +void SymbolTableView::setFilter(const std::string& filter) { + m_model->setFilter(filter); +} diff --git a/view/sharedcache/ui/symboltable.h b/view/sharedcache/ui/symboltable.h new file mode 100644 index 00000000..41e2597e --- /dev/null +++ b/view/sharedcache/ui/symboltable.h @@ -0,0 +1,87 @@ +#pragma once + +#include <sharedcacheapi.h> +#include "viewframe.h" +#include "animation.h" + +#include <QTableView> +#include <QStandardItemModel> +#include "filter.h" + +class SymbolTableView; + +class SymbolTableModel : public QAbstractTableModel { + Q_OBJECT + + SymbolTableView* m_parent; + std::string m_filter; + std::vector<SharedCacheAPI::CacheSymbol> 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; + + void updateSymbols(); + + void setFilter(std::string text); + + const SharedCacheAPI::CacheSymbol& symbolAt(int row) const; +}; + + +class SymbolTableView : public QTableView, public FilterTarget +{ + Q_OBJECT + friend class SymbolTableModel; + + // TODO: Both the model and the view store the symbols? + std::vector<SharedCacheAPI::CacheSymbol> m_symbols; + + SymbolTableModel* m_model; + +public: + SymbolTableView(QWidget* parent); + virtual ~SymbolTableView() override; + + void scrollToFirstItem() override { + if (model()->rowCount() > 0) { + scrollTo(model()->index(0, 0)); + } + } + + // Call this to populate the symbols from the given view. + void populateSymbols(BinaryNinja::BinaryView& view); + + void scrollToCurrentItem() override { + QModelIndex currentIndex = selectionModel()->currentIndex(); + if (currentIndex.isValid()) { + scrollTo(currentIndex); + } + } + + void selectFirstItem() override { + if (model()->rowCount() > 0) { + QModelIndex firstIndex = model()->index(0, 0); + selectionModel()->select(firstIndex, QItemSelectionModel::ClearAndSelect); + } + } + + void activateFirstItem() override { + if (model()->rowCount() > 0) { + QModelIndex firstIndex = model()->index(0, 0); + setCurrentIndex(firstIndex); + emit activated(firstIndex); + } + } + + SharedCacheAPI::CacheSymbol getSymbolAtRow(int row) const + { + return m_model->symbolAt(row); + } + + void setFilter(const std::string& filter) override; +};
\ No newline at end of file |
