#include #include #include "exports.h" #include "view.h" #include "fontsettings.h" const int OrdinalColumn = 0; const int AddressColumn = 1; const int NameColumn = 2; const int ColumnCount = 3; const int ColumnVisibleRole = Qt::UserRole; GenericExportsModel::GenericExportsModel(QWidget* parent, BinaryViewRef data): QAbstractItemModel(parent), BinaryDataNotification(FunctionUpdates | SymbolUpdates) { m_sortOrder = Qt::AscendingOrder; m_data = data; m_hasOrdinals = false; if (data->GetTypeName() == "PE") { m_hasOrdinals = true; } m_updateTimer = new QTimer(this); m_updateTimer->setSingleShot(true); m_updateTimer->setInterval(500); connect(m_updateTimer, &QTimer::timeout, this, &GenericExportsModel::updateModel); connect(this, &GenericExportsModel::modelUpdate, this, [=]() { if (m_updateTimer->isActive()) return; m_updateTimer->start(); }); m_data->RegisterNotification(this); updateModel(); m_entries = m_allEntries; } GenericExportsModel::~GenericExportsModel() { m_data->UnregisterNotification(this); } void GenericExportsModel::updateModel() { beginResetModel(); m_allEntries.clear(); for (auto& sym : m_data->GetSymbolsOfType(FunctionSymbol)) { if ((sym->GetBinding() == GlobalBinding) || (sym->GetBinding() == WeakBinding)) m_allEntries.push_back(sym); } for (auto& sym : m_data->GetSymbolsOfType(DataSymbol)) { if ((sym->GetBinding() == GlobalBinding) || (sym->GetBinding() == WeakBinding)) m_allEntries.push_back(sym); } endResetModel(); setFilter(m_filter); } int GenericExportsModel::columnCount(const QModelIndex&) const { return ColumnCount; } int GenericExportsModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; return (int)m_entries.size(); } QVariant GenericExportsModel::data(const QModelIndex& index, int role) const { switch (role) { case Qt::DisplayRole: if (role != Qt::DisplayRole) return QVariant(); if (!index.isValid() || index.row() >= (int)m_entries.size()) return QVariant(); if (index.column() == AddressColumn) return QString("0x") + QString::number(m_entries[index.row()]->GetAddress(), 16); if (index.column() == NameColumn) return QString::fromStdString(m_entries[index.row()]->GetFullName()); if (index.column() == OrdinalColumn) return QString::number(m_entries[index.row()]->GetOrdinal()); break; case Qt::ForegroundRole: if (index.column() == AddressColumn) return getThemeColor(AddressColor); if (index.column() == NameColumn) return getThemeColor(ExportColor); break; default: break; } return QVariant(); } QVariant GenericExportsModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Vertical) return QVariant(); if (role == ColumnVisibleRole) { if (section == OrdinalColumn) return QVariant(m_hasOrdinals); return true; } if (role != Qt::DisplayRole) return QVariant(); if (section == AddressColumn) return QString("Address"); if (section == NameColumn) return QString("Name"); if (section == OrdinalColumn) return QString("Ordinal"); return QVariant(); } QModelIndex GenericExportsModel::index(int row, int col, const QModelIndex& parent) const { if (parent.isValid()) return QModelIndex(); if (row >= (int)m_entries.size()) return QModelIndex(); if (col >= ColumnCount) return QModelIndex(); return createIndex(row, col); } QModelIndex GenericExportsModel::parent(const QModelIndex&) const { return QModelIndex(); } SymbolRef GenericExportsModel::getSymbol(const QModelIndex& index) { if (!index.isValid() || index.row() >= (int)m_entries.size()) return nullptr; return m_entries[index.row()]; } void GenericExportsModel::performSort(int col, Qt::SortOrder order) { std::sort(m_entries.begin(), m_entries.end(), [&](SymbolRef a, SymbolRef b) { if (col == AddressColumn) { if (a->GetAddress() != b->GetAddress()) { if (order == Qt::AscendingOrder) return a->GetAddress() < b->GetAddress(); else return a->GetAddress() > b->GetAddress(); } if (order == Qt::AscendingOrder) return a->GetFullName() < b->GetFullName(); else return a->GetFullName() > b->GetFullName(); } else if (col == NameColumn) { if (order == Qt::AscendingOrder) return a->GetFullName() < b->GetFullName(); else return a->GetFullName() > b->GetFullName(); } else if (col == OrdinalColumn) { if (a->GetOrdinal() != b->GetOrdinal()) { if (order == Qt::AscendingOrder) return a->GetOrdinal() < b->GetOrdinal(); else return a->GetOrdinal() > b->GetOrdinal(); } if (a->GetAddress() != b->GetAddress()) { if (order == Qt::AscendingOrder) return a->GetAddress() < b->GetAddress(); else return a->GetAddress() > b->GetAddress(); } if (order == Qt::AscendingOrder) return a->GetFullName() < b->GetFullName(); else return a->GetFullName() > b->GetFullName(); } return false; }); } void GenericExportsModel::sort(int col, Qt::SortOrder order) { beginResetModel(); m_sortCol = col; m_sortOrder = order; performSort(col, order); endResetModel(); } void GenericExportsModel::setFilter(const std::string& filterText) { m_filter = filterText; beginResetModel(); m_entries.clear(); for (auto& entry : m_allEntries) { if (FilteredView::match(entry->GetFullName(), filterText)) m_entries.push_back(entry); else if (FilteredView::match(std::to_string(entry->GetOrdinal()), filterText)) m_entries.push_back(entry); } performSort(m_sortCol, m_sortOrder); endResetModel(); } void GenericExportsModel::OnAnalysisFunctionAdded(BinaryNinja::BinaryView* view, BinaryNinja::Function* func) { emit modelUpdate(); } void GenericExportsModel::OnAnalysisFunctionRemoved(BinaryNinja::BinaryView* view, BinaryNinja::Function* func) { emit modelUpdate(); } void GenericExportsModel::OnAnalysisFunctionUpdated(BinaryNinja::BinaryView* view, BinaryNinja::Function* func) { emit modelUpdate(); } void GenericExportsModel::OnSymbolAdded(BinaryNinja::BinaryView* view, BinaryNinja::Symbol* sym) { emit modelUpdate(); } void GenericExportsModel::OnSymbolUpdated(BinaryNinja::BinaryView* view, BinaryNinja::Symbol* sym) { emit modelUpdate(); } void GenericExportsModel::OnSymbolRemoved(BinaryNinja::BinaryView* view, BinaryNinja::Symbol* sym) { emit modelUpdate(); } ExportsTreeView::ExportsTreeView(ExportsWidget* parent, TriageView* view, BinaryViewRef data) : QTreeView(parent) { m_data = data; m_parent = parent; m_view = view; m_selection.clear(); m_scroll = 0; // Allow view-specific shortcuts when imports are focused m_actionHandler.setupActionHandler(this); m_actionHandler.setActionContext([=]() { return m_view->actionContext(); }); setFont(getMonospaceFont(this)); m_model = new GenericExportsModel(this, m_data); setModel(m_model); setRootIsDecorated(false); setUniformRowHeights(true); setSortingEnabled(true); sortByColumn(AddressColumn, Qt::AscendingOrder); setColumnWidth(OrdinalColumn, 55); for (int i = 0; i < m_model->columnCount(QModelIndex()); i ++) { setColumnHidden(i, !m_model->headerData(i, Qt::Horizontal, ColumnVisibleRole).toBool()); } connect(selectionModel(), &QItemSelectionModel::currentChanged, this, &ExportsTreeView::exportSelected); connect(this, &QTreeView::doubleClicked, this, &ExportsTreeView::exportDoubleClicked); connect(m_model, &QAbstractItemModel::modelAboutToBeReset, this, [=]() { m_selection = selectionModel()->selectedIndexes(); m_scroll = verticalScrollBar()->value(); }); connect(m_model, &QAbstractItemModel::modelReset, this, [=]() { for (auto& idx : m_selection) { setCurrentIndex(idx); } verticalScrollBar()->setValue(m_scroll); }); } void ExportsTreeView::exportSelected(const QModelIndex& cur, const QModelIndex&) { SymbolRef sym = m_model->getSymbol(cur); if (sym) m_view->setCurrentOffset(sym->GetAddress()); } void ExportsTreeView::exportDoubleClicked(const QModelIndex& cur) { SymbolRef sym = m_model->getSymbol(cur); if (sym) { ViewFrame* viewFrame = ViewFrame::viewFrameForWidget(this); if (viewFrame) { if (BinaryNinja::Settings::Instance()->Get("ui.view.graph.preferred") && viewFrame->getCurrentBinaryView() && m_data->GetAnalysisFunctionsForAddress(sym->GetAddress()).size() > 0) { viewFrame->navigate("Graph:" + viewFrame->getCurrentDataType(), sym->GetAddress()); } else { viewFrame->navigate("Linear:" + viewFrame->getCurrentDataType(), sym->GetAddress()); } } } } void ExportsTreeView::setFilter(const std::string& filterText) { m_model->setFilter(filterText); } void ExportsTreeView::scrollToFirstItem() { scrollToTop(); } void ExportsTreeView::scrollToCurrentItem() { scrollTo(currentIndex()); } void ExportsTreeView::selectFirstItem() { setCurrentIndex(m_model->index(0, 0, QModelIndex())); } void ExportsTreeView::activateFirstItem() { exportDoubleClicked(m_model->index(0, 0, QModelIndex())); } void ExportsTreeView::closeFilter() { setFocus(Qt::OtherFocusReason); } void ExportsTreeView::keyPressEvent(QKeyEvent* event) { if ((event->text().size() == 1) && (event->text()[0] > ' ') && (event->text()[0] <= '~')) { m_parent->showFilter(event->text()); event->accept(); } else if ((event->key() == Qt::Key_Return) || (event->key() == Qt::Key_Enter)) { QList sel = selectionModel()->selectedIndexes(); if (sel.size() != 0) exportDoubleClicked(sel[0]); } QTreeView::keyPressEvent(event); } ExportsWidget::ExportsWidget(QWidget* parent, TriageView* view, BinaryViewRef data) : QWidget(parent) { QVBoxLayout* layout = new QVBoxLayout(); layout->setContentsMargins(0, 0, 0, 0); ExportsTreeView* exports = new ExportsTreeView(this, view, data); m_filter = new FilteredView(this, exports, exports); m_filter->setFilterPlaceholderText("Search exports"); layout->addWidget(m_filter, 1); setLayout(layout); setMinimumSize(UIContext::getScaledWindowSize(100, 196)); } void ExportsWidget::showFilter(const QString& filter) { m_filter->showFilter(filter); }