From c7d582a8932d5c1bc5f3717e732292ebae8b2b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Fri, 17 Aug 2018 13:27:07 +0200 Subject: [PATCH] Basic Display of Anal Classes in ClassesWidget Fetch Anal Classes Display vtable info in ClassesWidget Add Context Menu to ClassesWidget Show Base Classes in ClassesWidget Add EditMethodDialog Fix Anal Classes List Set Anal as the default Classes Source Display Anal Classes from SDB in ClassesWidget --- src/Cutter.cpp | 75 +++- src/Cutter.h | 34 +- src/Cutter.pro | 11 +- src/common/Json.h | 19 + src/dialogs/EditMethodDialog.cpp | 145 +++++++ src/dialogs/EditMethodDialog.h | 44 +++ src/dialogs/EditMethodDialog.ui | 125 ++++++ src/widgets/ClassesWidget.cpp | 633 +++++++++++++++++++++++-------- src/widgets/ClassesWidget.h | 81 ++-- src/widgets/ClassesWidget.ui | 17 +- 10 files changed, 974 insertions(+), 210 deletions(-) create mode 100644 src/common/Json.h create mode 100644 src/dialogs/EditMethodDialog.cpp create mode 100644 src/dialogs/EditMethodDialog.h create mode 100644 src/dialogs/EditMethodDialog.ui diff --git a/src/Cutter.cpp b/src/Cutter.cpp index e7a4e947..e9a9ee7d 100644 --- a/src/Cutter.cpp +++ b/src/Cutter.cpp @@ -8,6 +8,7 @@ #include "common/Configuration.h" #include "common/AsyncTask.h" #include "common/R2Task.h" +#include "common/Json.h" #include "Cutter.h" #include "sdb.h" @@ -537,6 +538,23 @@ void CutterCore::setCurrentBits(int bits, RVA offset) emit instructionChanged(offset); } +void CutterCore::setClassMethod(const QString &className, const ClassMethodDescription &meth) +{ + RAnalMethod analMeth; + analMeth.name = strdup (meth.name.toUtf8().constData()); + analMeth.addr = meth.addr; + analMeth.vtable_offset = meth.vtableOffset; + r_anal_class_method_set(core_->anal, className.toUtf8().constData(), &analMeth); + r_anal_class_method_fini(&analMeth); + emit classesChanged(); +} + +void CutterCore::renameClassMethod(const QString &className, const QString &oldMethodName, const QString &newMethodName) +{ + r_anal_class_method_rename(core_->anal, className.toUtf8().constData(), oldMethodName.toUtf8().constData(), newMethodName.toUtf8().constData()); + emit classesChanged(); +} + void CutterCore::seek(ut64 offset) { // Slower than using the API, but the API is not complete @@ -618,6 +636,17 @@ ut64 CutterCore::math(const QString &expr) return r_num_math(this->core_ ? this->core_->num : NULL, expr.toUtf8().constData()); } +ut64 CutterCore::num(const QString &expr) +{ + CORE_LOCK(); + return r_num_get(this->core_ ? this->core_->num : NULL, expr.toUtf8().constData()); +} + +QString CutterCore::itoa(ut64 num, int rdx) +{ + return QString::number(num, rdx); +} + void CutterCore::setConfig(const char *k, const QString &v) { CORE_LOCK(); @@ -1844,16 +1873,16 @@ QList CutterCore::getAllEntrypoint() return ret; } -QList CutterCore::getAllClassesFromBin() +QList CutterCore::getAllClassesFromBin() { CORE_LOCK(); - QList ret; + QList ret; QJsonArray classesArray = cmdj("icj").array(); for (const QJsonValue &value : classesArray) { QJsonObject classObject = value.toObject(); - ClassDescription cls; + BinClassDescription cls; cls.name = classObject[RJsonKey::classname].toString(); cls.addr = classObject[RJsonKey::addr].toVariant().toULongLong(); @@ -1886,16 +1915,14 @@ QList CutterCore::getAllClassesFromBin() return ret; } -#include - -QList CutterCore::getAllClassesFromFlags() +QList CutterCore::getAllClassesFromFlags() { static const QRegularExpression classFlagRegExp("^class\\.(.*)$"); static const QRegularExpression methodFlagRegExp("^method\\.([^\\.]*)\\.(.*)$"); CORE_LOCK(); - QList ret; - QMap classesCache; + QList ret; + QMap classesCache; QJsonArray flagsArray = cmdj("fj@F:classes").array(); for (const QJsonValue &value : flagsArray) { @@ -1906,10 +1933,10 @@ QList CutterCore::getAllClassesFromFlags() QRegularExpressionMatch match = classFlagRegExp.match(flagName); if (match.hasMatch()) { QString className = match.captured(1); - ClassDescription *desc = nullptr; + BinClassDescription *desc = nullptr; auto it = classesCache.find(className); if (it == classesCache.end()) { - ClassDescription cls = {}; + BinClassDescription cls = {}; ret << cls; desc = &ret.last(); classesCache[className] = desc; @@ -1918,20 +1945,20 @@ QList CutterCore::getAllClassesFromFlags() } desc->name = match.captured(1); desc->addr = flagObject[RJsonKey::offset].toVariant().toULongLong(); - desc->index = 0; + desc->index = RVA_INVALID; continue; } match = methodFlagRegExp.match(flagName); if (match.hasMatch()) { QString className = match.captured(1); - ClassDescription *classDesc = nullptr; + BinClassDescription *classDesc = nullptr; auto it = classesCache.find(className); if (it == classesCache.end()) { // add a new stub class, will be replaced if class flag comes after it - ClassDescription cls; + BinClassDescription cls; cls.name = tr("Unknown (%1)").arg(className); - cls.addr = 0; + cls.addr = RVA_INVALID; cls.index = 0; ret << cls; classDesc = &ret.last(); @@ -1950,6 +1977,26 @@ QList CutterCore::getAllClassesFromFlags() return ret; } +QList CutterCore::getAllClassesFromAnal() +{ + QList ret; + + SdbList *l = r_anal_class_get_all(core_->anal, true); + if (!l) { + return ret; + } + + SdbListIter *it; + void *entry; + ls_foreach(l, it, entry) { + auto kv = reinterpret_cast(entry); + ret.append(QString::fromUtf8(reinterpret_cast(kv->base.key))); + } + ls_free(l); + + return ret; +} + QList CutterCore::getAllResources() { CORE_LOCK(); diff --git a/src/Cutter.h b/src/Cutter.h index 99b5f723..d492afbb 100644 --- a/src/Cutter.h +++ b/src/Cutter.h @@ -251,20 +251,28 @@ struct DisassemblyLine { QString text; }; +struct ClassBaseClassDescription { + QString name; + RVA offset; +}; + struct ClassMethodDescription { QString name; - RVA addr; + RVA addr = RVA_INVALID; + st64 vtableOffset = -1; }; struct ClassFieldDescription { QString name; - RVA addr; + RVA addr = RVA_INVALID; }; -struct ClassDescription { +struct BinClassDescription { QString name; - RVA addr; - ut64 index; + RVA addr = RVA_INVALID; + RVA vtableAddr = RVA_INVALID; + ut64 index = 0; + QList baseClasses; QList methods; QList fields; }; @@ -357,8 +365,8 @@ Q_DECLARE_METATYPE(RCorePluginDescription) Q_DECLARE_METATYPE(RAsmPluginDescription) Q_DECLARE_METATYPE(ClassMethodDescription) Q_DECLARE_METATYPE(ClassFieldDescription) -Q_DECLARE_METATYPE(ClassDescription) -Q_DECLARE_METATYPE(const ClassDescription *) +Q_DECLARE_METATYPE(BinClassDescription) +Q_DECLARE_METATYPE(const BinClassDescription *) Q_DECLARE_METATYPE(const ClassMethodDescription *) Q_DECLARE_METATYPE(const ClassFieldDescription *) Q_DECLARE_METATYPE(ResourcesDescription) @@ -446,6 +454,10 @@ public: void setImmediateBase(const QString &r2BaseName, RVA offset = RVA_INVALID); void setCurrentBits(int bits, RVA offset = RVA_INVALID); + /* Classes */ + void setClassMethod(const QString &className, const ClassMethodDescription &meth); + void renameClassMethod(const QString &className, const QString &oldMethodName, const QString &newMethodName); + /* File related methods */ bool loadFile(QString path, ut64 baddr = 0LL, ut64 mapaddr = 0LL, int perms = R_PERM_R, int va = 0, bool loadbin = false, const QString &forceBinPlugin = QString()); @@ -481,6 +493,8 @@ public: /* Math functions */ ut64 math(const QString &expr); + ut64 num(const QString &expr); + QString itoa(ut64 num, int rdx = 16); /* Config functions */ void setConfig(const char *k, const QString &v); @@ -603,8 +617,9 @@ public: QList getAllSections(); QList getAllSegments(); QList getAllEntrypoint(); - QList getAllClassesFromBin(); - QList getAllClassesFromFlags(); + QList getAllClassesFromBin(); + QList getAllClassesFromFlags(); + QList getAllClassesFromAnal(); QList getAllResources(); QList getAllVTables(); @@ -677,6 +692,7 @@ signals: void functionsChanged(); void flagsChanged(); void commentsChanged(); + void classesChanged(); void registersChanged(); void instructionChanged(RVA offset); void breakpointsChanged(); diff --git a/src/Cutter.pro b/src/Cutter.pro index 723a4402..4ee6f90d 100644 --- a/src/Cutter.pro +++ b/src/Cutter.pro @@ -226,8 +226,8 @@ SOURCES += \ common/CutterSeekable.cpp \ common/RefreshDeferrer.cpp \ dialogs/WelcomeDialog.cpp \ - RunScriptTask.cpp - + RunScriptTask.cpp \ + dialogs/EditMethodDialog.cpp HEADERS += \ Cutter.h \ @@ -335,7 +335,9 @@ HEADERS += \ common/CutterSeekable.h \ common/RefreshDeferrer.h \ dialogs/WelcomeDialog.h \ - RunScriptTask.h + RunScriptTask.h \ + common/Json.h \ + dialogs/EditMethodDialog.h FORMS += \ dialogs/AboutDialog.ui \ @@ -394,7 +396,8 @@ FORMS += \ widgets/CutterTreeView.ui \ widgets/ComboQuickFilterView.ui \ dialogs/HexdumpRangeDialog.ui \ - dialogs/WelcomeDialog.ui + dialogs/WelcomeDialog.ui \ + dialogs/EditMethodDialog.ui RESOURCES += \ resources.qrc \ diff --git a/src/common/Json.h b/src/common/Json.h new file mode 100644 index 00000000..5848582f --- /dev/null +++ b/src/common/Json.h @@ -0,0 +1,19 @@ + +#ifndef CUTTER_JSON_H +#define CUTTER_JSON_H + +#include "Cutter.h" + +#include + +static inline RVA JsonValueGetRVA(const QJsonValue &value, RVA defaultValue = RVA_INVALID) +{ + bool ok; + RVA ret = value.toVariant().toULongLong(&ok); + if (!ok) { + return defaultValue; + } + return ret; +} + +#endif //CUTTER_JSON_H diff --git a/src/dialogs/EditMethodDialog.cpp b/src/dialogs/EditMethodDialog.cpp new file mode 100644 index 00000000..f3d9e6b7 --- /dev/null +++ b/src/dialogs/EditMethodDialog.cpp @@ -0,0 +1,145 @@ +#include "EditMethodDialog.h" +#include "ui_EditMethodDialog.h" + +EditMethodDialog::EditMethodDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::EditMethodDialog) +{ + ui->setupUi(this); + setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); + + ui->classComboBox->clear(); + /* TODO for (auto &cls : Core()->getAllClassesFromAnal()) { + ui->classComboBox->addItem(cls.name, QVariant::fromValue(cls)); + }*/ + + updateVirtualUI(); + validateInput(); + + connect(ui->virtualCheckBox, &QCheckBox::stateChanged, this, &EditMethodDialog::updateVirtualUI); + connect(ui->nameEdit, &QLineEdit::textChanged, this, &EditMethodDialog::validateInput); +} + +EditMethodDialog::~EditMethodDialog() {} + +void EditMethodDialog::on_buttonBox_accepted() +{ +} + +void EditMethodDialog::on_buttonBox_rejected() +{ + close(); +} + +void EditMethodDialog::updateVirtualUI() +{ + bool enabled = ui->virtualCheckBox->isChecked(); + ui->vtableOffsetEdit->setEnabled(enabled); + ui->vtableOffsetLabel->setEnabled(enabled); +} + +void EditMethodDialog::validateInput() +{ + for (auto button : ui->buttonBox->buttons()) { + if (ui->buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { + button->setEnabled(inputValid()); + return; + } + } +} + +bool EditMethodDialog::inputValid() +{ + if (ui->nameEdit->text().isEmpty()) { + return false; + } + return true; +} + +void EditMethodDialog::setClass(const QString &className) +{ + if (className.isEmpty()) { + ui->classComboBox->setCurrentIndex(0); + return; + } + + for (int i=0; iclassComboBox->count(); i++) { + BinClassDescription cls = ui->classComboBox->itemData(i).value(); + if (cls.name == className) { + ui->classComboBox->setCurrentIndex(i); + break; + } + } + + validateInput(); +} + +void EditMethodDialog::setMethod(const ClassMethodDescription &meth) +{ + ui->nameEdit->setText(meth.name); + ui->addressEdit->setText(meth.addr != RVA_INVALID ? RAddressString(meth.addr) : nullptr); + + if (meth.vtableOffset >= 0) { + ui->virtualCheckBox->setChecked(true); + ui->vtableOffsetEdit->setText(QString::number(meth.vtableOffset)); + } else { + ui->virtualCheckBox->setChecked(false); + ui->vtableOffsetEdit->setText(nullptr); + } + + updateVirtualUI(); + validateInput(); +} + +QString EditMethodDialog::getClass() +{ + int index = ui->classComboBox->currentIndex(); + if (index < 0) { + return nullptr; + } + return ui->classComboBox->itemData(index).value().name; +} + +ClassMethodDescription EditMethodDialog::getMethod() +{ + ClassMethodDescription ret; + ret.name = ui->nameEdit->text(); + ret.addr = Core()->num(ui->addressEdit->text()); + if (ui->virtualCheckBox->isChecked()) { + ret.vtableOffset = -1; + } else { + ret.vtableOffset = Core()->num(ui->vtableOffsetEdit->text()); + } + return ret; +} + +bool EditMethodDialog::showDialog(const QString &title, const QString &className, ClassMethodDescription *meth, QWidget *parent) +{ + auto dialog = new EditMethodDialog(parent); + dialog->setWindowTitle(title); + dialog->setClass(className); + dialog->setMethod(*meth); + int result = dialog->exec(); + *meth = dialog->getMethod(); + return result == QDialog::DialogCode::Accepted; +} + +void EditMethodDialog::newMethod(const QString &className, ClassMethodDescription meth) +{ + if (!showDialog(tr("Create Method"), className, &meth)) { + return; + } + Core()->setClassMethod(className, meth); +} + +void EditMethodDialog::editMethod(const QString &className, ClassMethodDescription meth) +{ + QString oldName = meth.name; + if (!showDialog(tr("Edit Method"), className, &meth)) { + return; + } + if (meth.name != oldName) { + Core()->renameClassMethod(className, oldName, meth.name); + } + Core()->setClassMethod(className, meth); +} \ No newline at end of file diff --git a/src/dialogs/EditMethodDialog.h b/src/dialogs/EditMethodDialog.h new file mode 100644 index 00000000..d4be06ce --- /dev/null +++ b/src/dialogs/EditMethodDialog.h @@ -0,0 +1,44 @@ +#ifndef EDITMETHODDIALOG_H +#define EDITMETHODDIALOG_H + +#include +#include + +#include "Cutter.h" + +namespace Ui { +class EditMethodDialog; +} + +class EditMethodDialog : public QDialog +{ + Q_OBJECT + +public: + explicit EditMethodDialog(QWidget *parent = nullptr); + ~EditMethodDialog(); + + void setClass(const QString &className); + void setMethod(const ClassMethodDescription &meth); + + QString getClass(); + ClassMethodDescription getMethod(); + + static bool showDialog(const QString &title, const QString &className, ClassMethodDescription *meth, QWidget *parent = nullptr); + static void newMethod(const QString &className = nullptr, ClassMethodDescription meth = ClassMethodDescription()); + static void editMethod(const QString &className, ClassMethodDescription meth); + +private slots: + void on_buttonBox_accepted(); + void on_buttonBox_rejected(); + + void updateVirtualUI(); + void validateInput(); + +private: + std::unique_ptr ui; + + bool inputValid(); +}; + +#endif // EDITMETHODDIALOG_H diff --git a/src/dialogs/EditMethodDialog.ui b/src/dialogs/EditMethodDialog.ui new file mode 100644 index 00000000..1e345f38 --- /dev/null +++ b/src/dialogs/EditMethodDialog.ui @@ -0,0 +1,125 @@ + + + EditMethodDialog + + + Qt::NonModal + + + + 0 + 0 + 461 + 238 + + + + Method + + + + + + + + Class: + + + + + + + + + + Name: + + + + + + + + + + Address: + + + + + + + + + + Virtual: + + + + + + + + + + + + + + Offset in VTable: + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + EditMethodDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + EditMethodDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/widgets/ClassesWidget.cpp b/src/widgets/ClassesWidget.cpp index 2897846f..b510fb84 100644 --- a/src/widgets/ClassesWidget.cpp +++ b/src/widgets/ClassesWidget.cpp @@ -5,146 +5,11 @@ #include "MainWindow.h" #include "ui_ClassesWidget.h" #include "common/Helpers.h" +#include "dialogs/EditMethodDialog.h" -ClassesModel::ClassesModel(QList *classes, QObject *parent) - : QAbstractItemModel(parent), - classes(classes) -{ -} +#include -QModelIndex ClassesModel::index(int row, int column, const QModelIndex &parent) const -{ - if (!parent.isValid()) - return createIndex(row, column, (quintptr)0); // root function nodes have id = 0 - - return createIndex(row, column, (quintptr)parent.row() + 1); // sub-nodes have id = class index + 1 -} - -QModelIndex ClassesModel::parent(const QModelIndex &index) const -{ - if (!index.isValid() || index.column() != 0) - return QModelIndex(); - - if (index.internalId() == 0) // root function node - return QModelIndex(); - else // sub-node - return this->index((int)(index.internalId() - 1), 0); -} - -int ClassesModel::rowCount(const QModelIndex &parent) const -{ - if (!parent.isValid()) { // root - return classes->count(); - } - - if (parent.internalId() == 0) { // methods/fields - const ClassDescription *cls = &classes->at(parent.row()); - return cls->methods.length() + cls->fields.length(); - } - - return 0; // below methods/fields -} - -int ClassesModel::columnCount(const QModelIndex &) const -{ - return Columns::COUNT; -} - -QVariant ClassesModel::data(const QModelIndex &index, int role) const -{ - const ClassDescription *cls; - const ClassMethodDescription *meth = nullptr; - const ClassFieldDescription *field = nullptr; - if (index.internalId() == 0) { // class row - if (index.row() >= classes->count()) { - return QVariant(); - } - - cls = &classes->at(index.row()); - } else { // method/field row - cls = &classes->at(static_cast(index.internalId() - 1)); - - if (index.row() >= cls->methods.length() + cls->fields.length()) { - return QVariant(); - } - - if (index.row() < cls->methods.length()) { - meth = &cls->methods[index.row()]; - } else { - field = &cls->fields[index.row() - cls->methods.length()]; - } - } - - if (meth) { - switch (role) { - case Qt::DisplayRole: - switch (index.column()) { - case NAME: - return meth->name; - case TYPE: - return tr("method"); - case OFFSET: - return RAddressString(meth->addr); - default: - return QVariant(); - } - case OffsetRole: - return QVariant::fromValue(meth->addr); - case NameRole: - return meth->name; - case TypeRole: - return QVariant::fromValue(METHOD); - default: - return QVariant(); - } - } else if (field) { - switch (role) { - case Qt::DisplayRole: - switch (index.column()) { - case NAME: - return field->name; - case TYPE: - return tr("field"); - case OFFSET: - return RAddressString(field->addr); - default: - return QVariant(); - } - case OffsetRole: - return QVariant::fromValue(field->addr); - case NameRole: - return field->name; - case TypeRole: - return QVariant::fromValue(FIELD); - default: - return QVariant(); - } - } else { - switch (role) { - case Qt::DisplayRole: - switch (index.column()) { - case NAME: - return cls->name; - case TYPE: - return tr("class"); - case OFFSET: - return RAddressString(cls->addr); - default: - return QVariant(); - } - case OffsetRole: - return QVariant::fromValue(cls->addr); - case NameRole: - return cls->name; - case TypeRole: - return QVariant::fromValue(CLASS); - default: - return QVariant(); - } - } -} - QVariant ClassesModel::headerData(int section, Qt::Orientation, int role) const { switch (role) { @@ -156,6 +21,8 @@ QVariant ClassesModel::headerData(int section, Qt::Orientation, int role) const return tr("Type"); case OFFSET: return tr("Offset"); + case VTABLE: + return tr("VTable"); default: return QVariant(); } @@ -166,13 +33,377 @@ QVariant ClassesModel::headerData(int section, Qt::Orientation, int role) const +BinClassesModel::BinClassesModel(QObject *parent) + : ClassesModel(parent) +{ +} + +void BinClassesModel::setClasses(const QList &classes) +{ + beginResetModel(); + this->classes = classes; + endResetModel(); +} -ClassesSortFilterProxyModel::ClassesSortFilterProxyModel(ClassesModel *source_model, - QObject *parent) +QModelIndex BinClassesModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!parent.isValid()) + return createIndex(row, column, (quintptr)0); // root function nodes have id = 0 + + return createIndex(row, column, (quintptr)parent.row() + 1); // sub-nodes have id = class index + 1 +} + +QModelIndex BinClassesModel::parent(const QModelIndex &index) const +{ + if (!index.isValid() || index.column() != 0) + return QModelIndex(); + + if (index.internalId() == 0) // root function node + return QModelIndex(); + else // sub-node + return this->index((int)(index.internalId() - 1), 0); +} + +int BinClassesModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) { // root + return classes.count(); + } + + if (parent.internalId() == 0) { // methods/fields + const BinClassDescription *cls = &classes.at(parent.row()); + return cls->baseClasses.length() + cls->methods.length() + cls->fields.length(); + } + + return 0; // below methods/fields +} + +int BinClassesModel::columnCount(const QModelIndex &) const +{ + return Columns::COUNT; +} + +QVariant BinClassesModel::data(const QModelIndex &index, int role) const +{ + const BinClassDescription *cls; + const ClassMethodDescription *meth = nullptr; + const ClassFieldDescription *field = nullptr; + const ClassBaseClassDescription *base = nullptr; + if (index.internalId() == 0) { // class row + if (index.row() >= classes.count()) { + return QVariant(); + } + + cls = &classes.at(index.row()); + } else { // method/field/base row + cls = &classes.at(static_cast(index.internalId() - 1)); + + if (index.row() >= cls->baseClasses.length() + cls->methods.length() + cls->fields.length()) { + return QVariant(); + } + + if (index.row() < cls->baseClasses.length()) { + base = &cls->baseClasses[index.row()]; + } else if (index.row() - cls->baseClasses.length() < cls->methods.length()) { + meth = &cls->methods[index.row() - cls->baseClasses.length()]; + } else { + field = &cls->fields[index.row() - cls->baseClasses.length() - cls->methods.length()]; + } + } + + if (meth) { + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case NAME: + return meth->name; + case TYPE: + return tr("method"); + case OFFSET: + return meth->addr == RVA_INVALID ? QString() : RAddressString(meth->addr); + case VTABLE: + return meth->vtableOffset < 0 ? QString() : QString("+%1").arg(meth->vtableOffset); + default: + return QVariant(); + } + case OffsetRole: + return QVariant::fromValue(meth->addr); + case VTableOffsetRole: + return QVariant::fromValue(index.parent().data(VTableOffsetRole).toULongLong() + meth->vtableOffset); + case NameRole: + return meth->name; + case TypeRole: + return QVariant::fromValue(METHOD); + case DataRole: + return QVariant::fromValue(*meth); + default: + return QVariant(); + } + } else if (field) { + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case NAME: + return field->name; + case TYPE: + return tr("field"); + case OFFSET: + return field->addr == RVA_INVALID ? QString() : RAddressString(field->addr); + default: + return QVariant(); + } + case OffsetRole: + return QVariant::fromValue(field->addr); + case NameRole: + return field->name; + case TypeRole: + return QVariant::fromValue(FIELD); + default: + return QVariant(); + } + } else if (base) { + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case NAME: + return base->name; + case TYPE: + return tr("base class"); + case OFFSET: + return QString("+%1").arg(base->offset); + default: + return QVariant(); + } + case NameRole: + return base->name; + case TypeRole: + return QVariant::fromValue(BASE); + default: + return QVariant(); + } + } + else { + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case NAME: + return cls->name; + case TYPE: + return tr("class"); + case OFFSET: + return cls->addr == RVA_INVALID ? QString() : RAddressString(cls->addr); + case VTABLE: + return cls->vtableAddr == RVA_INVALID ? QString() : RAddressString(cls->vtableAddr); + default: + return QVariant(); + } + case OffsetRole: + return QVariant::fromValue(cls->addr); + case VTableOffsetRole: + return QVariant::fromValue(cls->vtableAddr); + case NameRole: + return cls->name; + case TypeRole: + return QVariant::fromValue(CLASS); + default: + return QVariant(); + } + } +} + + +AnalClassesModel::AnalClassesModel(QObject *parent) + : ClassesModel(parent) +{ +} + +void AnalClassesModel::setClasses(const QList &classes) +{ + beginResetModel(); + this->classes = classes; + endResetModel(); +} + + +QModelIndex AnalClassesModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!parent.isValid()) + return createIndex(row, column, (quintptr)0); // root function nodes have id = 0 + + return createIndex(row, column, (quintptr)parent.row() + 1); // sub-nodes have id = class index + 1 +} + +QModelIndex AnalClassesModel::parent(const QModelIndex &index) const +{ + if (!index.isValid() || index.column() != 0) + return QModelIndex(); + + if (index.internalId() == 0) // root function node + return QModelIndex(); + else // sub-node + return this->index((int)(index.internalId() - 1), 0); +} + +int AnalClassesModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) { // root + return classes.count(); + } + + if (parent.internalId() == 0) { // methods/fields + return 0; + // TODO const BinClassDescription *cls = &classes.at(parent.row()); + // TODO return cls->baseClasses.length() + cls->methods.length() + cls->fields.length(); + } + + return 0; // below methods/fields +} + +bool AnalClassesModel::hasChildren(const QModelIndex &parent) const +{ + return true; +} + +int AnalClassesModel::columnCount(const QModelIndex &) const +{ + return Columns::COUNT; +} + +QVariant AnalClassesModel::data(const QModelIndex &index, int role) const +{ + QString cls; + const ClassMethodDescription *meth = nullptr; + const ClassFieldDescription *field = nullptr; + const ClassBaseClassDescription *base = nullptr; + if (index.internalId() == 0) { // class row + if (index.row() >= classes.count()) { + return QVariant(); + } + + cls = classes.at(index.row()); + } else { // method/field/base row + cls = classes.at(static_cast(index.internalId() - 1)); + + /*if (index.row() >= cls->baseClasses.length() + cls->methods.length() + cls->fields.length()) { + return QVariant(); + } + + if (index.row() < cls->baseClasses.length()) { + base = &cls->baseClasses[index.row()]; + } else if (index.row() - cls->baseClasses.length() < cls->methods.length()) { + meth = &cls->methods[index.row() - cls->baseClasses.length()]; + } else { + field = &cls->fields[index.row() - cls->baseClasses.length() - cls->methods.length()]; + }*/ + } + + if (meth) { + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case NAME: + return meth->name; + case TYPE: + return tr("method"); + case OFFSET: + return meth->addr == RVA_INVALID ? QString() : RAddressString(meth->addr); + case VTABLE: + return meth->vtableOffset < 0 ? QString() : QString("+%1").arg(meth->vtableOffset); + default: + return QVariant(); + } + case OffsetRole: + return QVariant::fromValue(meth->addr); + case VTableOffsetRole: + return QVariant::fromValue(index.parent().data(VTableOffsetRole).toULongLong() + meth->vtableOffset); + case NameRole: + return meth->name; + case TypeRole: + return QVariant::fromValue(METHOD); + case DataRole: + return QVariant::fromValue(*meth); + default: + return QVariant(); + } + } else if (field) { + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case NAME: + return field->name; + case TYPE: + return tr("field"); + case OFFSET: + return field->addr == RVA_INVALID ? QString() : RAddressString(field->addr); + default: + return QVariant(); + } + case OffsetRole: + return QVariant::fromValue(field->addr); + case NameRole: + return field->name; + case TypeRole: + return QVariant::fromValue(FIELD); + default: + return QVariant(); + } + } else if (base) { + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case NAME: + return base->name; + case TYPE: + return tr("base class"); + case OFFSET: + return QString("+%1").arg(base->offset); + default: + return QVariant(); + } + case NameRole: + return base->name; + case TypeRole: + return QVariant::fromValue(BASE); + default: + return QVariant(); + } + } + else { + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case NAME: + return cls; + case TYPE: + return tr("class"); + // TODO case OFFSET: + // TODO return cls->addr == RVA_INVALID ? QString() : RAddressString(cls->addr); + // TODO case VTABLE: + // TODO return cls->vtableAddr == RVA_INVALID ? QString() : RAddressString(cls->vtableAddr); + default: + return QVariant(); + } + // TODO case OffsetRole: + // TODO return QVariant::fromValue(cls->addr); + // TODO case VTableOffsetRole: + // TODO return QVariant::fromValue(cls->vtableAddr); + // TODO case NameRole: + // TODO return cls->name; + case TypeRole: + return QVariant::fromValue(CLASS); + default: + return QVariant(); + } + } +} + + + + +ClassesSortFilterProxyModel::ClassesSortFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent) { - setSourceModel(source_model); } bool ClassesSortFilterProxyModel::filterAcceptsRow(int row, const QModelIndex &parent) const @@ -214,41 +445,61 @@ ClassesWidget::ClassesWidget(MainWindow *main, QAction *action) : { ui->setupUi(this); - model = new ClassesModel(&classes, this); - proxy_model = new ClassesSortFilterProxyModel(model, this); - ui->classesTreeView->setModel(proxy_model); + proxy_model = new ClassesSortFilterProxyModel(this); + ui->classesTreeView->setModel(nullptr); ui->classesTreeView->sortByColumn(ClassesModel::TYPE, Qt::AscendingOrder); + ui->classesTreeView->setContextMenuPolicy(Qt::CustomContextMenu); + + ui->classSourceCombo->setCurrentIndex(1); connect(Core(), SIGNAL(refreshAll()), this, SLOT(refreshClasses())); - connect(Core(), SIGNAL(flagsChanged()), this, SLOT(flagsChanged())); + connect(Core(), &CutterCore::classesChanged, this, [this]() { + if (getSource() == Source::ANAL) { + refreshClasses(); + } + }); connect(ui->classSourceCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(refreshClasses())); + connect(ui->classesTreeView, &QTreeView::customContextMenuRequested, this, &ClassesWidget::showContextMenu); } ClassesWidget::~ClassesWidget() {} ClassesWidget::Source ClassesWidget::getSource() { - if (ui->classSourceCombo->currentIndex() == 1) { - return Source::FLAGS; - } else { + switch (ui->classSourceCombo->currentIndex()) { + case 0: return Source::BIN; - } -} - -void ClassesWidget::flagsChanged() -{ - if (getSource() == Source::FLAGS) { - refreshClasses(); + default: + return Source::ANAL; } } void ClassesWidget::refreshClasses() { - model->beginResetModel(); - classes = getSource() == Source::BIN - ? Core()->getAllClassesFromBin() - : Core()->getAllClassesFromFlags(); - model->endResetModel(); + switch (getSource()) { + case Source::BIN: + if (!bin_model) { + proxy_model->setSourceModel(nullptr); + delete anal_model; + anal_model = nullptr; + bin_model = new BinClassesModel(this); + ui->classesTreeView->setModel(bin_model); + //proxy_model->setSourceModel(bin_model); + } + bin_model->setClasses(Core()->getAllClassesFromBin()); + break; + case Source::ANAL: + if (!anal_model) { + proxy_model->setSourceModel(nullptr); + delete bin_model; + bin_model = nullptr; + anal_model = new AnalClassesModel(this); + ui->classesTreeView->setModel(anal_model); + //proxy_model->setSourceModel(anal_model); + } + anal_model->setClasses(Core()->getAllClassesFromAnal()); + break; + } qhelpers::adjustColumns(ui->classesTreeView, 3, 0); @@ -263,3 +514,67 @@ void ClassesWidget::on_classesTreeView_doubleClicked(const QModelIndex &index) RVA offset = index.data(ClassesModel::OffsetRole).value(); Core()->seek(offset); } + +void ClassesWidget::showContextMenu(const QPoint &pt) +{ + QModelIndex index = ui->classesTreeView->selectionModel()->currentIndex(); + if (!index.isValid()) { + return; + } + + QMenu menu(ui->classesTreeView); + + QVariant vtableOffsetVariant = index.data(ClassesModel::VTableOffsetRole); + if (vtableOffsetVariant.isValid() && vtableOffsetVariant.toULongLong() != RVA_INVALID) { + menu.addAction(ui->seekToVTableAction); + } + + menu.addAction(ui->addMethodAction); + + if (index.data(ClassesModel::TypeRole) == ClassesModel::METHOD) { + menu.addAction(ui->editMethodAction); + } + + menu.exec(ui->classesTreeView->mapToGlobal(pt)); +} + +void ClassesWidget::on_seekToVTableAction_triggered() +{ + RVA vtableOffset = ui->classesTreeView->selectionModel()->currentIndex() + .data(ClassesModel::VTableOffsetRole).value(); + if (vtableOffset != RVA_INVALID) { + Core()->seek(vtableOffset); + } +} + +void ClassesWidget::on_addMethodAction_triggered() +{ + QModelIndex index = ui->classesTreeView->selectionModel()->currentIndex(); + if (!index.isValid()) { + return; + } + + QString className; + if (index.data(ClassesModel::TypeRole).toInt() == ClassesModel::CLASS) { + className = index.data(ClassesModel::NameRole).toString(); + } else { + className = index.parent().data(ClassesModel::NameRole).toString(); + } + + ClassMethodDescription meth; + meth.addr = Core()->getOffset(); + + EditMethodDialog::newMethod(className, meth); +} + +void ClassesWidget::on_editMethodAction_triggered() +{ + QModelIndex index = ui->classesTreeView->selectionModel()->currentIndex(); + if (!index.isValid() || index.data(ClassesModel::TypeRole).toInt() != ClassesModel::METHOD) { + return; + } + + QString className = index.parent().data(ClassesModel::NameRole).toString(); + ClassMethodDescription meth = index.data(ClassesModel::DataRole).value(); + EditMethodDialog::editMethod(className, meth); +} diff --git a/src/widgets/ClassesWidget.h b/src/widgets/ClassesWidget.h index 61bd8f66..0a58b27a 100644 --- a/src/widgets/ClassesWidget.h +++ b/src/widgets/ClassesWidget.h @@ -18,46 +18,76 @@ class QTreeWidgetItem; class MainWindow; class ClassesWidget; - class ClassesModel: public QAbstractItemModel { - Q_OBJECT - - friend ClassesWidget; - -private: - QList *classes; - public: - enum Columns { NAME = 0, TYPE, OFFSET, COUNT }; - enum RowType { CLASS = 0, METHOD = 1, FIELD = 2 }; + enum Columns { NAME = 0, TYPE, OFFSET, VTABLE, COUNT }; + enum RowType { CLASS = 0, METHOD = 1, FIELD = 2, BASE = 3 }; static const int OffsetRole = Qt::UserRole; static const int NameRole = Qt::UserRole + 1; static const int TypeRole = Qt::UserRole + 2; + static const int VTableOffsetRole = Qt::UserRole + 3; + static const int DataRole = Qt::UserRole + 4; - explicit ClassesModel(QList *classes, QObject *parent = nullptr); + explicit ClassesModel(QObject *parent = nullptr) : QAbstractItemModel(parent) {} - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex &index) const override; - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex &parent = QModelIndex()) const override; - - QVariant data(const QModelIndex &index, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; }; Q_DECLARE_METATYPE(ClassesModel::RowType) +class BinClassesModel: public ClassesModel +{ + Q_OBJECT + +private: + QList classes; + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + + QModelIndex parent(const QModelIndex &index) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + +public: + explicit BinClassesModel(QObject *parent = nullptr); + void setClasses(const QList &classes); +}; + + +class AnalClassesModel: public ClassesModel +{ +Q_OBJECT + +private: + QList classes; + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + + QModelIndex parent(const QModelIndex &index) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; + + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + +public: + explicit AnalClassesModel(QObject *parent = nullptr); + void setClasses(const QList &classes); +}; + + class ClassesSortFilterProxyModel : public QSortFilterProxyModel { Q_OBJECT public: - explicit ClassesSortFilterProxyModel(ClassesModel *source_model, QObject *parent = nullptr); + explicit ClassesSortFilterProxyModel(QObject *parent = nullptr); protected: bool filterAcceptsRow(int row, const QModelIndex &parent) const override; @@ -77,19 +107,24 @@ public: private slots: void on_classesTreeView_doubleClicked(const QModelIndex &index); + void on_seekToVTableAction_triggered(); + void on_addMethodAction_triggered(); + void on_editMethodAction_triggered(); + + void showContextMenu(const QPoint &pt); + void refreshClasses(); - void flagsChanged(); private: - enum class Source { BIN, FLAGS }; + enum class Source { BIN, ANAL }; Source getSource(); std::unique_ptr ui; - ClassesModel *model; + BinClassesModel *bin_model = nullptr; + AnalClassesModel *anal_model = nullptr; ClassesSortFilterProxyModel *proxy_model; - QList classes; }; diff --git a/src/widgets/ClassesWidget.ui b/src/widgets/ClassesWidget.ui index 29554a1c..c22eb697 100644 --- a/src/widgets/ClassesWidget.ui +++ b/src/widgets/ClassesWidget.ui @@ -95,7 +95,7 @@ - Flags (Editable) + Anal (Editable) @@ -104,6 +104,21 @@ + + + Seek to VTable + + + + + Edit Method + + + + + Add Method + +