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
This commit is contained in:
Florian Märkl 2018-08-17 13:27:07 +02:00
parent 601339d86f
commit c7d582a893
10 changed files with 974 additions and 210 deletions

View File

@ -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<EntrypointDescription> CutterCore::getAllEntrypoint()
return ret;
}
QList<ClassDescription> CutterCore::getAllClassesFromBin()
QList<BinClassDescription> CutterCore::getAllClassesFromBin()
{
CORE_LOCK();
QList<ClassDescription> ret;
QList<BinClassDescription> 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<ClassDescription> CutterCore::getAllClassesFromBin()
return ret;
}
#include <QList>
QList<ClassDescription> CutterCore::getAllClassesFromFlags()
QList<BinClassDescription> CutterCore::getAllClassesFromFlags()
{
static const QRegularExpression classFlagRegExp("^class\\.(.*)$");
static const QRegularExpression methodFlagRegExp("^method\\.([^\\.]*)\\.(.*)$");
CORE_LOCK();
QList<ClassDescription> ret;
QMap<QString, ClassDescription *> classesCache;
QList<BinClassDescription> ret;
QMap<QString, BinClassDescription *> classesCache;
QJsonArray flagsArray = cmdj("fj@F:classes").array();
for (const QJsonValue &value : flagsArray) {
@ -1906,10 +1933,10 @@ QList<ClassDescription> 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<ClassDescription> 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<ClassDescription> CutterCore::getAllClassesFromFlags()
return ret;
}
QList<QString> CutterCore::getAllClassesFromAnal()
{
QList<QString> 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<SdbKv *>(entry);
ret.append(QString::fromUtf8(reinterpret_cast<const char *>(kv->base.key)));
}
ls_free(l);
return ret;
}
QList<ResourcesDescription> CutterCore::getAllResources()
{
CORE_LOCK();

View File

@ -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<ClassBaseClassDescription> baseClasses;
QList<ClassMethodDescription> methods;
QList<ClassFieldDescription> 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<SectionDescription> getAllSections();
QList<SegmentDescription> getAllSegments();
QList<EntrypointDescription> getAllEntrypoint();
QList<ClassDescription> getAllClassesFromBin();
QList<ClassDescription> getAllClassesFromFlags();
QList<BinClassDescription> getAllClassesFromBin();
QList<BinClassDescription> getAllClassesFromFlags();
QList<QString> getAllClassesFromAnal();
QList<ResourcesDescription> getAllResources();
QList<VTableDescription> getAllVTables();
@ -677,6 +692,7 @@ signals:
void functionsChanged();
void flagsChanged();
void commentsChanged();
void classesChanged();
void registersChanged();
void instructionChanged(RVA offset);
void breakpointsChanged();

View File

@ -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 \

19
src/common/Json.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef CUTTER_JSON_H
#define CUTTER_JSON_H
#include "Cutter.h"
#include <QJsonValue>
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

View File

@ -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<BinClassDescription>(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; i<ui->classComboBox->count(); i++) {
BinClassDescription cls = ui->classComboBox->itemData(i).value<BinClassDescription>();
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<BinClassDescription>().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);
}

View File

@ -0,0 +1,44 @@
#ifndef EDITMETHODDIALOG_H
#define EDITMETHODDIALOG_H
#include <QDialog>
#include <memory>
#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::EditMethodDialog> ui;
bool inputValid();
};
#endif // EDITMETHODDIALOG_H

View File

@ -0,0 +1,125 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>EditMethodDialog</class>
<widget class="QDialog" name="EditMethodDialog">
<property name="windowModality">
<enum>Qt::NonModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>461</width>
<height>238</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">Method</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Class:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="classComboBox"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="nameLabel">
<property name="text">
<string>Name:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="nameEdit"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="addressLabel">
<property name="text">
<string>Address:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="addressEdit"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="virtualLabel">
<property name="text">
<string>Virtual:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="virtualCheckBox">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="vtableOffsetLabel">
<property name="text">
<string>Offset in VTable:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="vtableOffsetEdit"/>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>EditMethodDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>EditMethodDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -5,146 +5,11 @@
#include "MainWindow.h"
#include "ui_ClassesWidget.h"
#include "common/Helpers.h"
#include "dialogs/EditMethodDialog.h"
ClassesModel::ClassesModel(QList<ClassDescription> *classes, QObject *parent)
: QAbstractItemModel(parent),
classes(classes)
{
}
#include <QMenu>
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<int>(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<BinClassDescription> &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<int>(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<QString> &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<int>(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<RVA>();
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<RVA>();
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<ClassMethodDescription>();
EditMethodDialog::editMethod(className, meth);
}

View File

@ -18,46 +18,76 @@ class QTreeWidgetItem;
class MainWindow;
class ClassesWidget;
class ClassesModel: public QAbstractItemModel
{
Q_OBJECT
friend ClassesWidget;
private:
QList<ClassDescription> *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<ClassDescription> *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<BinClassDescription> 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<BinClassDescription> &classes);
};
class AnalClassesModel: public ClassesModel
{
Q_OBJECT
private:
QList<QString> 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<QString> &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::ClassesWidget> ui;
ClassesModel *model;
BinClassesModel *bin_model = nullptr;
AnalClassesModel *anal_model = nullptr;
ClassesSortFilterProxyModel *proxy_model;
QList<ClassDescription> classes;
};

View File

@ -95,7 +95,7 @@
</item>
<item>
<property name="text">
<string>Flags (Editable)</string>
<string>Anal (Editable)</string>
</property>
</item>
</widget>
@ -104,6 +104,21 @@
</item>
</layout>
</widget>
<action name="seekToVTableAction">
<property name="text">
<string>Seek to VTable</string>
</property>
</action>
<action name="editMethodAction">
<property name="text">
<string>Edit Method</string>
</property>
</action>
<action name="addMethodAction">
<property name="text">
<string>Add Method</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>