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/dsctriage.cpp | |
| 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/dsctriage.cpp')
| -rw-r--r-- | view/sharedcache/ui/dsctriage.cpp | 1135 |
1 files changed, 289 insertions, 846 deletions
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()); } |
