From c1132aba0cd55a4548bef3b9777357e15f93972c Mon Sep 17 00:00:00 2001 From: Abdel-Rahman A Date: Tue, 27 Feb 2018 00:26:18 +0200 Subject: [PATCH] Add virtual tables panel (#340) --- src/MainWindow.cpp | 3 + src/MainWindow.h | 2 + src/MainWindow.ui | 12 +++ src/cutter.cpp | 30 ++++++ src/cutter.h | 8 ++ src/cutter.pro | 7 +- src/widgets/VTablesWidget.cpp | 179 ++++++++++++++++++++++++++++++++++ src/widgets/VTablesWidget.h | 70 +++++++++++++ src/widgets/VTablesWidget.ui | 93 ++++++++++++++++++ 9 files changed, 402 insertions(+), 2 deletions(-) create mode 100644 src/widgets/VTablesWidget.cpp create mode 100644 src/widgets/VTablesWidget.h create mode 100644 src/widgets/VTablesWidget.ui diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 1bd37965..153ae1c3 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -65,6 +65,7 @@ #include "dialogs/SaveProjectDialog.h" #include "widgets/ClassesWidget.h" #include "widgets/ResourcesWidget.h" +#include "widgets/VTablesWidget.h" // graphics #include @@ -223,6 +224,7 @@ void MainWindow::initUI() ADD_DOCK(SdbDock, sdbDock, ui->actionSDBBrowser); ADD_DOCK(ClassesWidget, classesDock, ui->actionClasses); ADD_DOCK(ResourcesWidget, resourcesDock, ui->actionResources); + ADD_DOCK(VTablesWidget, vTablesDock, ui->actionVTables); #undef ADD_DOCK @@ -545,6 +547,7 @@ void MainWindow::restoreDocks() tabifyDockWidget(dashboardDock, notepadDock); tabifyDockWidget(dashboardDock, classesDock); tabifyDockWidget(dashboardDock, resourcesDock); + tabifyDockWidget(dashboardDock, vTablesDock); updateDockActionsChecked(); } diff --git a/src/MainWindow.h b/src/MainWindow.h index b0ad5c7b..1b9c6fb9 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -39,6 +39,7 @@ class EntrypointWidget; class DisassemblerGraphView; class ClassesWidget; class ResourcesWidget; +class VTablesWidget; class QDockWidget; @@ -186,6 +187,7 @@ private: ConsoleWidget *consoleDock = nullptr; ClassesWidget *classesDock = nullptr; ResourcesWidget *resourcesDock = nullptr; + VTablesWidget *vTablesDock = nullptr; DisassemblerGraphView *graphView = nullptr; QDockWidget *asmDock = nullptr; QDockWidget *calcDock = nullptr; diff --git a/src/MainWindow.ui b/src/MainWindow.ui index fce548b6..fbec1117 100644 --- a/src/MainWindow.ui +++ b/src/MainWindow.ui @@ -250,6 +250,7 @@ border-top: 0px; + @@ -1050,6 +1051,17 @@ QToolButton:pressed { Resources + + + true + + + VTables + + + Show/Hide VTables panel + + diff --git a/src/cutter.cpp b/src/cutter.cpp index d89ecd25..6961acb2 100644 --- a/src/cutter.cpp +++ b/src/cutter.cpp @@ -1252,6 +1252,36 @@ QList CutterCore::getAllResources() return ret; } +QList CutterCore::getAllVTables() +{ + CORE_LOCK(); + QList ret; + + QJsonArray vTablesArray = cmdj("avj").array(); + for(QJsonValueRef vTableValue : vTablesArray) + { + QJsonObject vTableObject = vTableValue.toObject(); + + VTableDescription res; + res.addr = vTableObject["offset"].toVariant().toULongLong(); + QJsonArray methodArray = vTableObject["methods"].toArray(); + + for(QJsonValueRef methodValue : methodArray) + { + QJsonObject methodObject = methodValue.toObject(); + + ClassMethodDescription method; + method.addr = methodObject["offset"].toVariant().toULongLong(); + method.name = methodObject["name"].toString(); + + res.methods << method; + } + + ret << res; + } + return ret; +} + QList CutterCore::getXRefs(RVA addr, bool to, bool whole_function, const QString &filterType) { QList ret = QList(); diff --git a/src/cutter.h b/src/cutter.h index c13b52b7..e32ef66e 100644 --- a/src/cutter.h +++ b/src/cutter.h @@ -208,6 +208,12 @@ struct ResourcesDescription QString lang; }; +struct VTableDescription +{ + RVA addr; + QList methods; +}; + Q_DECLARE_METATYPE(FunctionDescription) Q_DECLARE_METATYPE(ImportDescription) Q_DECLARE_METATYPE(ExportDescription) @@ -227,6 +233,7 @@ Q_DECLARE_METATYPE(const ClassDescription *) Q_DECLARE_METATYPE(const ClassMethodDescription *) Q_DECLARE_METATYPE(const ClassFieldDescription *) Q_DECLARE_METATYPE(ResourcesDescription) +Q_DECLARE_METATYPE(VTableDescription) class CutterCore: public QObject { @@ -360,6 +367,7 @@ public: QList getAllEntrypoint(); QList getAllClasses(); QList getAllResources(); + QList getAllVTables(); QList getXRefs(RVA addr, bool to, bool whole_function, const QString &filterType = QString::null); diff --git a/src/cutter.pro b/src/cutter.pro index d5bc892d..010fd81a 100644 --- a/src/cutter.pro +++ b/src/cutter.pro @@ -90,6 +90,7 @@ SOURCES += \ widgets/QuickFilterView.cpp \ widgets/ClassesWidget.cpp \ widgets/ResourcesWidget.cpp \ + widgets/VTablesWidget.cpp \ CutterApplication.cpp HEADERS += \ @@ -150,7 +151,8 @@ HEADERS += \ widgets/QuickFilterView.h \ widgets/ClassesWidget.h \ widgets/ResourcesWidget.h \ - CutterApplication.h + CutterApplication.h \ + widgets/VTablesWidget.h FORMS += \ dialogs/AboutDialog.ui \ @@ -186,7 +188,8 @@ FORMS += \ dialogs/preferences/GraphOptionsWidget.ui \ widgets/QuickFilterView.ui \ widgets/PseudocodeWidget.ui \ - widgets/ClassesWidget.ui + widgets/ClassesWidget.ui \ + widgets/VTablesWidget.ui RESOURCES += \ resources.qrc \ diff --git a/src/widgets/VTablesWidget.cpp b/src/widgets/VTablesWidget.cpp new file mode 100644 index 00000000..5f85a93a --- /dev/null +++ b/src/widgets/VTablesWidget.cpp @@ -0,0 +1,179 @@ +#include + +#include "VTablesWidget.h" +#include "ui_VTablesWidget.h" + +VTableModel::VTableModel(QList *vtables, QObject *parent) + : QAbstractItemModel(parent), + vtables(vtables) +{ +} + +QModelIndex VTableModel::index(int row, int column, const QModelIndex &parent) const +{ + return createIndex(row, column, (quintptr) parent.isValid()? parent.row() : -1); +} + +QModelIndex VTableModel::parent(const QModelIndex &index) const +{ + return index.isValid() && index.internalId() != (quintptr) -1 ? + this->index(index.internalId(), index.column()) : QModelIndex(); +} + +int VTableModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid()? (parent.parent().isValid()? 0 : vtables->at(parent.row()).methods.count()) : vtables->count(); +} + +int VTableModel::columnCount(const QModelIndex &) const +{ + return Columns::COUNT; +} + +QVariant VTableModel::data(const QModelIndex &index, int role) const +{ + QModelIndex parent = index.parent(); + if(parent.isValid()) + { + const ClassMethodDescription &res = vtables->at(parent.row()).methods.at(index.row()); + switch (role) + { + case Qt::DisplayRole: + switch(index.column()) + { + case NAME: + return res.name; + case ADDRESS: + return RAddressString(res.addr); + default: + break; + } + default: + break; + } + } + else + switch(role) + { + case Qt::DisplayRole: + switch(index.column()) + { + case NAME: + return tr("VTable ") + QString::number(index.row() + 1); + case ADDRESS: + return RAddressString(vtables->at(index.row()).addr); + default: + break; + } + default: + break; + } + return QVariant(); +} + +QVariant VTableModel::headerData(int section, Qt::Orientation, int role) const +{ + switch(role) + { + case Qt::DisplayRole: + switch(section) + { + case NAME: + return tr("Name"); + case ADDRESS: + return tr("Address"); + default: + break; + } + default: + break; + } + return QVariant(); +} + +void VTableModel::beginReload() +{ + beginResetModel(); +} + +void VTableModel::endReload() +{ + endResetModel(); +} + +VTableSortFilterProxyModel::VTableSortFilterProxyModel(VTableModel *model, QObject* parent) + : QSortFilterProxyModel(parent) +{ + setSourceModel(model); + setFilterCaseSensitivity(Qt::CaseInsensitive); + setSortCaseSensitivity(Qt::CaseInsensitive); + setFilterKeyColumn(VTableModel::NAME); +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + setRecursiveFilteringEnabled(true); +#endif +} + +bool VTableSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + if(QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent)) + return true; + if(source_parent.isValid()) + return QSortFilterProxyModel::filterAcceptsRow(source_parent.row(), QModelIndex()); +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) + else + { + QAbstractItemModel* const model = sourceModel(); + const QModelIndex source = model->index(source_row, 0, QModelIndex()); + const int rows = model->rowCount(source); + for(int i = 0; i < rows; ++i) + if(QSortFilterProxyModel::filterAcceptsRow(i, source)) + return true; + } +#endif + return false; +} + + +VTablesWidget::VTablesWidget(QWidget *parent) : + QDockWidget(parent), + ui(new Ui::VTablesWidget) +{ + ui->setupUi(this); + + model = new VTableModel(&vtables, this); + proxy = new VTableSortFilterProxyModel(model); + + ui->vTableTreeView->setModel(proxy); + ui->vTableTreeView->sortByColumn(VTableModel::ADDRESS, Qt::AscendingOrder); + + // Esc to clear the filter entry + QShortcut *clear_shortcut = new QShortcut(QKeySequence(Qt::Key_Escape), this); + connect(clear_shortcut, &QShortcut::activated, ui->quickFilterView, &QuickFilterView::clearFilter); + + // Ctrl-F to show/hide the filter entry + QShortcut *search_shortcut = new QShortcut(QKeySequence::Find, this); + connect(search_shortcut, &QShortcut::activated, ui->quickFilterView, &QuickFilterView::showFilter); + search_shortcut->setContext(Qt::WidgetWithChildrenShortcut); + + connect(ui->quickFilterView, SIGNAL(filterTextChanged(const QString&)), proxy, SLOT(setFilterWildcard(const QString &))); + connect(ui->quickFilterView, SIGNAL(filterClosed()), ui->vTableTreeView, SLOT(setFocus())); + + connect(Core(), SIGNAL(refreshAll()), this, SLOT(refreshVTables())); +} + +VTablesWidget::~VTablesWidget() +{ +} + +void VTablesWidget::refreshVTables() +{ + model->beginReload(); + vtables = CutterCore::getInstance()->getAllVTables(); + model->endReload(); + + ui->vTableTreeView->resizeColumnToContents(0); + ui->vTableTreeView->resizeColumnToContents(1); + ui->vTableTreeView->resizeColumnToContents(2); + + ui->vTableTreeView->setColumnWidth(0, 200); +} diff --git a/src/widgets/VTablesWidget.h b/src/widgets/VTablesWidget.h new file mode 100644 index 00000000..fe549094 --- /dev/null +++ b/src/widgets/VTablesWidget.h @@ -0,0 +1,70 @@ +#ifndef VTABLESWIDGET_H +#define VTABLESWIDGET_H + +#include + +#include +#include +#include + +#include "cutter.h" + +namespace Ui +{ + class VTablesWidget; +} + +class VTableModel : public QAbstractItemModel +{ + Q_OBJECT + +private: + QList *vtables; + +public: + enum Columns { NAME = 0, ADDRESS, COUNT }; + + VTableModel(QList *vtables, QObject* parent = nullptr); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &index) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + void beginReload(); + void endReload(); +}; + +class VTableSortFilterProxyModel : public QSortFilterProxyModel +{ +public: + VTableSortFilterProxyModel(VTableModel* model, QObject *parent = nullptr); + +protected: + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; +}; + +class VTablesWidget : public QDockWidget +{ + Q_OBJECT + +public: + explicit VTablesWidget(QWidget *parent = 0); + ~VTablesWidget(); + +private slots: + void refreshVTables(); + +private: + std::unique_ptr ui; + + VTableModel *model; + QSortFilterProxyModel *proxy; + QList vtables; +}; + +#endif // VTABLESWIDGET_H diff --git a/src/widgets/VTablesWidget.ui b/src/widgets/VTablesWidget.ui new file mode 100644 index 00000000..50899b40 --- /dev/null +++ b/src/widgets/VTablesWidget.ui @@ -0,0 +1,93 @@ + + + VTablesWidget + + + + + + &VTable + + + + + 1 + 0 + + + + + 200 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Qt::CustomContextMenu + + + +QTreeView::item +{ + padding-top: 1px; + padding-bottom: 1px; +} + + + + QFrame::NoFrame + + + 0 + + + QAbstractScrollArea::AdjustToContents + + + 8 + + + true + + + + + + + + + + + + QuickFilterView + QWidget +
widgets/QuickFilterView.h
+ 1 +
+
+ + +