From 161f8b4b51ae5520d91652271f854d0b40dc0e91 Mon Sep 17 00:00:00 2001 From: NewtronReal Date: Fri, 19 Jun 2026 10:40:10 +0530 Subject: [PATCH 01/13] Initial Work on DiffLoadWindow clang formatted new changes --- rizin | 2 +- src/CMakeLists.txt | 19 +++- src/core/MainWindow.cpp | 9 ++ src/core/MainWindow.h | 2 + src/core/MainWindow.ui | 14 ++- src/tools/bindiff/DiffLoadDialog.cpp | 106 ++++++++++++++++++ src/tools/bindiff/DiffLoadDialog.h | 40 +++++++ src/tools/bindiff/DiffLoadDialog.ui | 162 +++++++++++++++++++++++++++ src/translations | 2 +- 9 files changed, 348 insertions(+), 8 deletions(-) create mode 100644 src/tools/bindiff/DiffLoadDialog.cpp create mode 100644 src/tools/bindiff/DiffLoadDialog.h create mode 100644 src/tools/bindiff/DiffLoadDialog.ui diff --git a/rizin b/rizin index da228d11cf..c351d5f32a 160000 --- a/rizin +++ b/rizin @@ -1 +1 @@ -Subproject commit da228d11cfc865b06442bd66b192caeb6e5ff556 +Subproject commit c351d5f32a5c17a61a6ff7931ab6cb5059a3d1e5 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4fba081a05..6c7c3bca1f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -174,6 +174,12 @@ set(SOURCES dialogs/preferences/RizinConfigOptionsWidget.cpp dialogs/IntervalDialog.cpp dialogs/StringListDialog.cpp + tools/bindiff/DiffLoadDialog.cpp + core/BinDiff.cpp + core/CutterDiff.cpp + tools/bindiff/CutterDiffWindow.cpp + tools/bindiff/DiffWaitDialog.cpp + tools/bindiff/HexDiff.cpp ) set(HEADER_FILES core/Cutter.h @@ -356,6 +362,12 @@ set(HEADER_FILES dialogs/preferences/RizinConfigOptionsWidget.h dialogs/IntervalDialog.h dialogs/StringListDialog.h + tools/bindiff/DiffLoadDialog.h + core/BinDiff.h + core/CutterDiff.h + tools/bindiff/CutterDiffWindow.h + tools/bindiff/DiffWaitDialog.h + tools/bindiff/HexDiff.h ) set(UI_FILES dialogs/AboutDialog.ui @@ -440,6 +452,9 @@ set(UI_FILES dialogs/preferences/SymbolsOptionsWidget.ui dialogs/preferences/InterfaceOptionsWidget.ui dialogs/preferences/RizinConfigOptionsWidget.ui + tools/bindiff/DiffLoadDialog.ui + tools/bindiff/CutterDiffWindow.ui + tools/bindiff/DiffWaitDialog.ui ) set(QRC_FILES resources.qrc @@ -602,11 +617,11 @@ if (CUTTER_APPIMAGE_BUILD) endif() if (CUTTER_PACKAGE_JSDEC) - target_compile_definitions(Cutter PRIVATE CUTTER_BUNDLE_JSDEC) + target_compile_definitions(Cutter PRIVATE CUTTER_BUNDLE_JSDEC) endif() if(APPLE AND CUTTER_ENABLE_PACKAGING AND CUTTER_USE_BUNDLED_RIZIN) - target_compile_definitions(Cutter PRIVATE MACOS_RZ_BUNDLED) + target_compile_definitions(Cutter PRIVATE MACOS_RZ_BUNDLED) endif() if(CUTTER_ENABLE_PACKAGING) diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp index e72be732d4..5488472ce6 100644 --- a/src/core/MainWindow.cpp +++ b/src/core/MainWindow.cpp @@ -115,6 +115,7 @@ // Tools #include "tools/basefind/BaseFindDialog.h" +#include "tools/bindiff/DiffLoadDialog.h" template T *getNewInstance(MainWindow *m) @@ -400,6 +401,8 @@ void MainWindow::initToolBar() connect(ui->actionResetSettings, &QAction::triggered, this, &MainWindow::onActionResetSettingsTriggered); connect(ui->actionTheme, &QAction::triggered, this, &MainWindow::chooseThemeIcons); + connect(ui->actionDiffFiles, &QAction::triggered, this, + &MainWindow::onActionDiffFilesTriggered); } void MainWindow::initDocks() @@ -1747,6 +1750,12 @@ void MainWindow::onActionRefreshPanelsTriggered() this->refreshAll(); } +void MainWindow::onActionDiffFilesTriggered() +{ + auto diffFilesDialog = new DiffLoadDialog(this); + diffFilesDialog->show(); +} + void MainWindow::onActionAnalyzeTriggered() const { auto *analysisTask = new AnalysisTask(); diff --git a/src/core/MainWindow.h b/src/core/MainWindow.h index b336e4858a..12615dcec2 100644 --- a/src/core/MainWindow.h +++ b/src/core/MainWindow.h @@ -184,6 +184,8 @@ private slots: void onActionDefaultTriggered(); + void onActionDiffFilesTriggered(); + /** * @brief MainWindow::on_actionNew_triggered * Open a new Cutter session. diff --git a/src/core/MainWindow.ui b/src/core/MainWindow.ui index 5661bfe342..8af4123993 100644 --- a/src/core/MainWindow.ui +++ b/src/core/MainWindow.ui @@ -51,10 +51,10 @@ - 2024 - 127 - 196 - 389 + 337 + 86 + 206 + 414 @@ -91,6 +91,7 @@ + @@ -878,6 +879,11 @@ Manage layouts + + + Diff Files + + diff --git a/src/tools/bindiff/DiffLoadDialog.cpp b/src/tools/bindiff/DiffLoadDialog.cpp new file mode 100644 index 0000000000..92b755920a --- /dev/null +++ b/src/tools/bindiff/DiffLoadDialog.cpp @@ -0,0 +1,106 @@ +#include "DiffLoadDialog.h" + +#include "ui_DiffLoadDialog.h" + +#include +#include + +#include +#include + +DiffLoadDialog::DiffLoadDialog(QWidget *parent) : QDialog(parent), ui(new Ui::DiffLoadDialog) +{ + ui->setupUi(this); + setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); + setModal(true); + + ui->lineEditFileA->setReadOnly(true); + ui->lineEditFileA->setText(""); + + ui->lineEditFileB->setReadOnly(true); + ui->lineEditFileB->setText(""); + + ui->comboBoxAnalysis->addItem(tr("Basic")); + ui->comboBoxAnalysis->addItem(tr("Auto")); + ui->comboBoxAnalysis->addItem(tr("Experimental")); + + ui->comboBoxCompare->addItem(tr("Default")); + ui->comboBoxCompare->addItem(tr("All functions")); + ui->comboBoxCompare->addItem(tr("Only Symbols")); + + connect(ui->buttonFileAOpen, &QPushButton::clicked, this, + &DiffLoadDialog::onButtonFileAOpenClicked); + connect(ui->buttonFileBOpen, &QPushButton::clicked, this, + &DiffLoadDialog::onButtonFileBOpenClicked); + + auto index = ui->comboBoxAnalysis->findData(tr("Auto"), Qt::DisplayRole); + ui->comboBoxAnalysis->setCurrentIndex(index); +} + +DiffLoadDialog::~DiffLoadDialog() {} + +QString DiffLoadDialog::getFileA() const +{ + return ui->lineEditFileA->text(); +} + +QString DiffLoadDialog::getFileB() const +{ + return ui->lineEditFileB->text(); +} + +int DiffLoadDialog::getLevel() const +{ + return ui->comboBoxAnalysis->currentIndex(); +} + +int DiffLoadDialog::getCompare() const +{ + return ui->comboBoxCompare->currentIndex(); +} + +void DiffLoadDialog::onButtonFileAOpenClicked() +{ + QFileDialog dialog(this); + dialog.setWindowTitle(tr("Select File 2")); + dialog.setNameFilters({ tr("All files (*)") }); + + if (!dialog.exec()) { + return; + } + + const QString &fileName = QDir::toNativeSeparators(dialog.selectedFiles().first()); + + if (fileName.isEmpty()) { + return; + } + + ui->lineEditFileB->setText(fileName); +} + +void DiffLoadDialog::onButtonFileBOpenClicked() +{ + QFileDialog dialog(this); + dialog.setWindowTitle(tr("Select File 2")); + dialog.setNameFilters({ tr("All files (*)") }); + + if (!dialog.exec()) { + return; + } + + const QString &fileName = QDir::toNativeSeparators(dialog.selectedFiles().first()); + + if (fileName.isEmpty()) { + return; + } + + ui->lineEditFileA->setText(fileName); +} + +void DiffLoadDialog::onButtonBoxAccepted() +{ + // Check files exists + emit startDiffing(); +} + +void DiffLoadDialog::onButtonBoxRejected() {} diff --git a/src/tools/bindiff/DiffLoadDialog.h b/src/tools/bindiff/DiffLoadDialog.h new file mode 100644 index 0000000000..a3aa748494 --- /dev/null +++ b/src/tools/bindiff/DiffLoadDialog.h @@ -0,0 +1,40 @@ +#ifndef DIFF_LOAD_DIALOG_H +#define DIFF_LOAD_DIALOG_H + +#include +#include + +#include +#include + +namespace Ui { +class DiffLoadDialog; +} + +class DiffLoadDialog : public QDialog +{ + Q_OBJECT + +public: + explicit DiffLoadDialog(QWidget *parent = nullptr); + ~DiffLoadDialog(); + + QString getFileA() const; + QString getFileB() const; + int getLevel() const; + int getCompare() const; + +signals: + void startDiffing(); + +private slots: + void onButtonFileAOpenClicked(); + void onButtonFileBOpenClicked(); + void onButtonBoxAccepted(); + void onButtonBoxRejected(); + +private: + std::unique_ptr ui; +}; + +#endif // DIFF_LOAD_DIALOG_H diff --git a/src/tools/bindiff/DiffLoadDialog.ui b/src/tools/bindiff/DiffLoadDialog.ui new file mode 100644 index 0000000000..f5b4563260 --- /dev/null +++ b/src/tools/bindiff/DiffLoadDialog.ui @@ -0,0 +1,162 @@ + + + DiffLoadDialog + + + Qt::WindowModality::NonModal + + + + 0 + 0 + 636 + 226 + + + + + 0 + 0 + + + + Select which file to compare. + + + + QLayout::SizeConstraint::SetMinimumSize + + + + + QLayout::SizeConstraint::SetMinimumSize + + + + + + + + Select + + + + + + + Select + + + + + + + + + + + + + + 0 + 0 + + + + <html><head/><body><p>You can load a previous saved diff instead of performing a new diff scan.</p></body></html> + + + File 2(required) + + + + + + + Analysis Level + + + + + + + + 0 + 0 + + + + <html><head/><body><p>File to compare to.</p></body></html> + + + File 1(required) + + + + + + + Compare Logic + + + + + + + + + + + + QLayout::SizeConstraint::SetMinimumSize + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + + + + + + + + + buttonBox + accepted() + DiffLoadDialog + accept() + + + 248 + 234 + + + 157 + 274 + + + + + buttonBox + rejected() + DiffLoadDialog + reject() + + + 316 + 240 + + + 286 + 254 + + + + + diff --git a/src/translations b/src/translations index fa68a9fec3..bc5aced406 160000 --- a/src/translations +++ b/src/translations @@ -1 +1 @@ -Subproject commit fa68a9fec31ad8b4a3bf8aab19de9c63d739c39e +Subproject commit bc5aced406678ae6f24fa501b53a7ae105c9f119 From 014da63564c67c061b6187300aabd8c7b8cf3565 Mon Sep 17 00:00:00 2001 From: NewtronReal Date: Fri, 19 Jun 2026 10:47:50 +0530 Subject: [PATCH 02/13] initial work on bindiff --- src/core/BinDiff.cpp | 161 +++++++++++++++++++++++++++++++++++++++++++ src/core/BinDiff.h | 54 +++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 src/core/BinDiff.cpp create mode 100644 src/core/BinDiff.h diff --git a/src/core/BinDiff.cpp b/src/core/BinDiff.cpp new file mode 100644 index 0000000000..cc2e3e6ec7 --- /dev/null +++ b/src/core/BinDiff.cpp @@ -0,0 +1,161 @@ +#include "BinDiff.h" + +bool BinDiff::threadCallback(const size_t nLeft, const size_t nMatch, void *user) +{ + auto bdiff = reinterpret_cast(user); + return bdiff->updateProgress(nLeft, nMatch); +} + +BinDiff::BinDiff() + : result(nullptr), + continueRun(true), + maxTotal(1) +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + , + mutex(QMutex::Recursive) +#endif +{ +} + +BinDiff::~BinDiff() +{ + // rz_analysis_match_result_free(result); +} + +bool BinDiff::hasData() +{ + return result != nullptr; +} + +void BinDiff::setFile(QString filePath) +{ + mutex.lock(); + file = filePath; + mutex.unlock(); +} + +void BinDiff::setAnalysisLevel(int aLevel) +{ + mutex.lock(); + level = aLevel; + mutex.unlock(); +} + +void BinDiff::setCompareLogic(int cLogic) +{ + mutex.lock(); + compareLogic = cLogic; + mutex.unlock(); +} + +void BinDiff::run() +{ + qRegisterMetaType(); + + mutex.lock(); + rz_analysis_match_result_free(result); + continueRun = true; + maxTotal = 1; // maxTotal must be at least 1. + mutex.unlock(); + + result = Core()->diffNewFile(file, level, compareLogic, threadCallback, this); + + Core()->diffData.setAnalysisMatchResult(result); + + mutex.lock(); + const bool canComplete = continueRun; + mutex.unlock(); + if (canComplete) { + emit complete(); + } +} + +void BinDiff::cancel() +{ + mutex.lock(); + continueRun = false; + mutex.unlock(); +} + +// static void setFunctionDescription(FunctionDescription *desc, const RzAnalysisFunction *func) +// { +// desc->offset = func->addr; +// desc->linearSize = rz_analysis_function_linear_size(const_cast(func)); +// desc->nargs = rz_analysis_arg_count(const_cast(func)); +// desc->nlocals = rz_analysis_var_local_count(const_cast(func)); +// desc->nbbs = rz_pvector_len(func->bbs); +// desc->calltype = func->cc ? QString::fromUtf8(func->cc) : QString(); +// desc->name = func->name ? QString::fromUtf8(func->name) : QString(); +// desc->edges = rz_analysis_function_count_edges(func, nullptr); +// desc->stackframe = func->maxstack; +// } + +// QList BinDiff::matches() +// { +// QList pairs; +// const RzAnalysisMatchPair *pair = nullptr; +// const RzListIter *it = nullptr; +// const RzAnalysisFunction *fcnA = nullptr; +// const RzAnalysisFunction *fcnB = nullptr; + +// if (!result) { +// return pairs; +// } + +// CutterRzListForeach (result->matches, it, RzAnalysisMatchPair, pair) { +// BinDiffMatchDescription desc; +// fcnA = static_cast(pair->pair_a); +// fcnB = static_cast(pair->pair_b); + +// setFunctionDescription(&desc.original, fcnA); +// setFunctionDescription(&desc.modified, fcnB); + +// desc.simtype = RZ_ANALYSIS_SIMILARITY_TYPE_STR(pair->similarity); +// desc.similarity = pair->similarity; + +// pairs.push_back(desc); +// } + +// return pairs; +// } + +// QList BinDiff::mismatch(bool originalFile) +// { +// QList list; +// if (!result) { +// return list; +// } + +// const RzAnalysisFunction *func = nullptr; +// const RzList *unmatch = originalFile ? result->unmatch_a : result->unmatch_b; +// const RzListIter *it = nullptr; + +// CutterRzListForeach (unmatch, it, RzAnalysisFunction, func) { +// FunctionDescription desc; +// setFunctionDescription(&desc, func); +// list.push_back(desc); +// } + +// return list; +// } + +bool BinDiff::updateProgress(size_t nLeft, size_t nMatch) +{ + mutex.lock(); + if (nMatch > maxTotal) { + maxTotal = nMatch; + } + if (nLeft > maxTotal) { + maxTotal = nLeft; + } + + BinDiffStatusDescription status; + status.total = maxTotal; + status.nLeft = nLeft; + status.nMatch = nMatch; + + emit progress(status); + bool ret = continueRun; + mutex.unlock(); + return ret; +} diff --git a/src/core/BinDiff.h b/src/core/BinDiff.h new file mode 100644 index 0000000000..99555d8758 --- /dev/null +++ b/src/core/BinDiff.h @@ -0,0 +1,54 @@ +#ifndef CUTTER_BINDIFF_CORE_H +#define CUTTER_BINDIFF_CORE_H + +#include "Cutter.h" +#include "CutterDescriptions.h" + +#include +#include + +#include + +class BinDiff : public QThread +{ + Q_OBJECT + +public: + explicit BinDiff(); + virtual ~BinDiff(); + + void run(); + + void setFile(QString filePth); + void setAnalysisLevel(int aLevel); + void setCompareLogic(int cLogic); + bool hasData(); + + QList matches(); + QList mismatch(bool originalFile); + +public slots: + void cancel(); + +signals: + void progress(BinDiffStatusDescription status); + void complete(); + +private: + RzAnalysisMatchResult *result; + bool continueRun; + size_t maxTotal; +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + QMutex mutex; +#else + QRecursiveMutex mutex; +#endif + QString file; + int level; + int compareLogic; + + bool updateProgress(const size_t nLeft, const size_t nMatch); + static bool threadCallback(const size_t nLeft, const size_t nMatch, void *user); +}; + +#endif // CUTTER_BINDIFF_CORE_H From 9aff9e6b862e340cc25aa6a1cea4922916a79345 Mon Sep 17 00:00:00 2001 From: NewtronReal Date: Fri, 19 Jun 2026 10:48:56 +0530 Subject: [PATCH 03/13] added new Descritptions --- src/core/CutterDescriptions.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/core/CutterDescriptions.h b/src/core/CutterDescriptions.h index 0f23398b04..a901034708 100644 --- a/src/core/CutterDescriptions.h +++ b/src/core/CutterDescriptions.h @@ -423,6 +423,21 @@ struct BasefindResultDescription quint32 score; }; +struct BinDiffMatchDescription +{ + FunctionDescription original; + FunctionDescription modified; + QString simtype; + double similarity; +}; + +struct BinDiffStatusDescription +{ + size_t total; + size_t nLeft; + size_t nMatch; +}; + struct MarkDescription { RVA from; @@ -496,6 +511,8 @@ Q_DECLARE_METATYPE(RefDescription) Q_DECLARE_METATYPE(VariableDescription) Q_DECLARE_METATYPE(BasefindCoreStatusDescription) Q_DECLARE_METATYPE(BasefindResultDescription) +Q_DECLARE_METATYPE(BinDiffMatchDescription) +Q_DECLARE_METATYPE(BinDiffStatusDescription) Q_DECLARE_METATYPE(MarkDescription) Q_DECLARE_METATYPE(BacktraceDescription) Q_DECLARE_METATYPE(EvaluableVarDescription) From 3671af317c219c32352f03b13b980307201e476e Mon Sep 17 00:00:00 2001 From: NewtronReal Date: Sat, 20 Jun 2026 12:45:42 +0530 Subject: [PATCH 04/13] added HexDiff widget --- src/tools/bindiff/CutterDiffWindow.cpp | 366 +++++ src/tools/bindiff/CutterDiffWindow.h | 139 ++ src/tools/bindiff/CutterDiffWindow.ui | 174 +++ src/tools/bindiff/DiffLoadDialog.cpp | 34 +- src/tools/bindiff/DiffLoadDialog.h | 6 +- src/tools/bindiff/DiffWaitDialog.cpp | 109 ++ src/tools/bindiff/DiffWaitDialog.h | 46 + src/tools/bindiff/DiffWaitDialog.ui | 151 ++ src/tools/bindiff/HexDiff.cpp | 1931 ++++++++++++++++++++++++ src/tools/bindiff/HexDiff.h | 494 ++++++ 10 files changed, 3447 insertions(+), 3 deletions(-) create mode 100644 src/tools/bindiff/CutterDiffWindow.cpp create mode 100644 src/tools/bindiff/CutterDiffWindow.h create mode 100644 src/tools/bindiff/CutterDiffWindow.ui create mode 100644 src/tools/bindiff/DiffWaitDialog.cpp create mode 100644 src/tools/bindiff/DiffWaitDialog.h create mode 100644 src/tools/bindiff/DiffWaitDialog.ui create mode 100644 src/tools/bindiff/HexDiff.cpp create mode 100644 src/tools/bindiff/HexDiff.h diff --git a/src/tools/bindiff/CutterDiffWindow.cpp b/src/tools/bindiff/CutterDiffWindow.cpp new file mode 100644 index 0000000000..e8e699812d --- /dev/null +++ b/src/tools/bindiff/CutterDiffWindow.cpp @@ -0,0 +1,366 @@ +#include "CutterDiffWindow.h" + +#include "DiffLoadDialog.h" +#include "ui_CutterDiffWindow.h" + +#include + +FunctionListModel::FunctionListModel(QList *list, QObject *parent) + : list(list), AddressableItemModel<>(parent) +{ +} +QModelIndex FunctionListModel::index(int row, int column, const QModelIndex &) const +{ + return createIndex(row, column, (quintptr)0); +} + +QModelIndex FunctionListModel::parent(const QModelIndex &) const +{ + return this->index(0, 0); +} + +int FunctionListModel::rowCount(const QModelIndex &) const +{ + return list->size(); +} + +int FunctionListModel::columnCount(const QModelIndex &) const +{ + return ColumnCount; +} + +QVariant FunctionListModel::data(const QModelIndex &index, int role) const +{ + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case Name: + return list->at(index.row()).name; + case Offset: + return list->at(index.row()).offset; + default: + return "unknown"; + } + case Qt::ToolTipRole: + switch (index.column()) { + case Name: + return list->at(index.row()).name; + case Offset: + return list->at(index.row()).offset; + default: + return "unknown"; + } + default: + return QVariant(); + } +} + +QVariant FunctionListModel::headerData(int section, Qt::Orientation, int role) const +{ + switch (role) { + case Qt::DisplayRole: + switch (section) { + case Name: + return "Function Name"; + case Offset: + return "Offset"; + default: + return QVariant(); + } + case Qt::ToolTipRole: + switch (section) { + case Name: + return "Name of the function."; + case Offset: + return "Address of the function."; + default: + return QVariant(); + } + default: + return QVariant(); + } +} + +RVA FunctionListModel::address(const QModelIndex &index) const +{ + return list->at(index.row()).offset; +} + +DiffMatchModel::DiffMatchModel(QList *list, QColor cPerf, QColor cPart, + QObject *parent) + : QAbstractListModel(parent), list(list), perfect(cPerf), partial(cPart) +{ +} + +int DiffMatchModel::rowCount(const QModelIndex &) const +{ + return list->count(); +} + +int DiffMatchModel::columnCount(const QModelIndex &) const +{ + return DiffMatchModel::ColumnCount; +} + +QVariant DiffMatchModel::data(const QModelIndex &index, int role) const +{ + if (index.row() >= list->count()) { + return QVariant(); + } + + const BinDiffMatchDescription &entry = list->at(index.row()); + + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case NameOrig: + return entry.original.name; + case SizeOrig: + return QString::asprintf("%llu (%#llx)", entry.original.linearSize, + entry.original.linearSize); + case AddressOrig: + return rzAddressString(entry.original.offset); + case Similarity: + return QString::asprintf("%.2f (%.2f %%)", entry.similarity, entry.similarity * 100.0); + case AddressMod: + return rzAddressString(entry.modified.offset); + case SizeMod: + return QString::asprintf("%llu (%#llx)", entry.modified.linearSize, + entry.modified.linearSize); + case NameMod: + return entry.modified.name; + default: + return QVariant(); + } + + case Qt::ToolTipRole: { + switch (index.column()) { + case NameOrig: + return entry.original.name; + case SizeOrig: + return QString::asprintf("%llu (%#llx)", entry.original.linearSize, + entry.original.linearSize); + case AddressOrig: + return rzAddressString(entry.original.offset); + case Similarity: + return entry.simtype; + case AddressMod: + return rzAddressString(entry.modified.offset); + case SizeMod: + return QString::asprintf("%llu (%#llx)", entry.modified.linearSize, + entry.modified.linearSize); + case NameMod: + return entry.modified.name; + default: + return QVariant(); + } + } + + case Qt::BackgroundRole: { + return gradientByRatio(entry.similarity); + } + + default: + return QVariant(); + } +} + +QVariant DiffMatchModel::headerData(int section, Qt::Orientation, int role) const +{ + switch (role) { + case Qt::DisplayRole: + switch (section) { + case NameOrig: + return tr("Name (A)"); + case SizeOrig: + return tr("Size (A)"); + case AddressOrig: + return tr("Address (A)"); + case Similarity: + return tr("Similarity"); + case AddressMod: + return tr("Address (B)"); + case SizeMod: + return tr("Size (B)"); + case NameMod: + return tr("Name (B)"); + default: + return QVariant(); + } + default: + return QVariant(); + } +} + +QColor DiffMatchModel::gradientByRatio(const double ratio) const +{ + const float red = partial.redF() + (ratio * (perfect.redF() - partial.redF())); + const float green = partial.greenF() + (ratio * (perfect.greenF() - partial.greenF())); + const float blue = partial.blueF() + (ratio * (perfect.blueF() - partial.blueF())); + return QColor::fromRgbF(red, green, blue); +} + +DiffMismatchModel::DiffMismatchModel(QList *list, QObject *parent) + : QAbstractListModel(parent), list(list) +{ +} + +int DiffMismatchModel::rowCount(const QModelIndex &) const +{ + return list->count(); +} + +int DiffMismatchModel::columnCount(const QModelIndex &) const +{ + return DiffMismatchModel::ColumnCount; +} + +QVariant DiffMismatchModel::data(const QModelIndex &index, int role) const +{ + if (index.row() >= list->count()) { + return QVariant(); + } + + const FunctionDescription &entry = list->at(index.row()); + + switch (role) { + case Qt::ToolTipRole: + /* fall-thru */ + case Qt::DisplayRole: + switch (index.column()) { + case FuncName: + return entry.name; + case FuncAddress: + return rzAddressString(entry.offset); + case FuncLinearSize: + return QString::asprintf("%llu (%#llx)", entry.linearSize, entry.linearSize); + case FuncNargs: + return QString::asprintf("%llu", entry.nargs); + case FuncNlocals: + return QString::asprintf("%llu", entry.nlocals); + case FuncNbbs: + return QString::asprintf("%llu", entry.nbbs); + case FuncCalltype: + return entry.calltype; + case FuncEdges: + return QString::asprintf("%llu", entry.edges); + case FuncStackframe: + return QString::asprintf("%llu", entry.stackframe); + default: + return QVariant(); + } + + default: + return QVariant(); + } +} + +QVariant DiffMismatchModel::headerData(int section, Qt::Orientation, int role) const +{ + switch (role) { + case Qt::DisplayRole: + switch (section) { + case FuncName: + return tr("Name"); + case FuncAddress: + return tr("Address"); + case FuncLinearSize: + return tr("Linear Size"); + case FuncNargs: + return tr("Num Args"); + case FuncNlocals: + return tr("Num Locals"); + case FuncNbbs: + return tr("Basic Blocks"); + case FuncCalltype: + return tr("Call Type"); + case FuncEdges: + return tr("Edges"); + case FuncStackframe: + return tr("Stackframe"); + default: + return QVariant(); + } + default: + return QVariant(); + } +} + +CutterDiffWindow::CutterDiffWindow(QWidget *parent) + : QMainWindow(parent), + cutterDiff(new CutterDiff), + bDiff(new BinDiff(cutterDiff)), + ui(new Ui::CutterDiffWindow) +{ + ui->setupUi(this); + cutterDiff->initCores(); + connect(bDiff, &BinDiff::complete, this, &CutterDiffWindow::onBinDiffCompleted); + connect(ui->actionDiffNewFiles, &QAction::triggered, this, + &CutterDiffWindow::onActionDiffNewFile); + setupFonts(); + refreshHex(RVA_INVALID); +} + +CutterDiffWindow::~CutterDiffWindow() +{ + delete ui; +} + +void CutterDiffWindow::setupFonts() +{ + const QFont font = Config()->getFont(); + ui->hexDiffTextView->setMonospaceFont(font); +} + +void CutterDiffWindow::refreshHex(RVA addr) +{ + if (addr != RVA_INVALID) { + ui->hexDiffTextView->seek(addr); + } else { + ui->hexDiffTextView->refresh(); + } +} + +void CutterDiffWindow::onBinDiffCompleted() +{ + if (!bDiff->hasData()) { + return; + } + + const QColor perfect = Config()->getColor("gui.match.perfect"); + const QColor partial = Config()->getColor("gui.match.partial"); + + listMatch = bDiff->matches(); + listDel = bDiff->mismatch(true); + listAdd = bDiff->mismatch(false); + + matches = new DiffMatchModel(&listMatch, perfect, partial, this); + added = new DiffMismatchModel(&listDel, this); + removed = new DiffMismatchModel(&listAdd, this); + + ui->treeViewMatches->setModel(matches); + ui->treeViewMatches->sortByColumn(DiffMatchModel::Similarity, Qt::AscendingOrder); + ui->treeViewMatches->setContextMenuPolicy(Qt::CustomContextMenu); + + ui->treeViewRemoved->setModel(removed); + ui->treeViewRemoved->sortByColumn(DiffMismatchModel::FuncName, Qt::AscendingOrder); + ui->treeViewRemoved->setContextMenuPolicy(Qt::CustomContextMenu); + + ui->treeViewAdded->setModel(added); + ui->treeViewAdded->sortByColumn(DiffMismatchModel::FuncName, Qt::AscendingOrder); + ui->treeViewAdded->setContextMenuPolicy(Qt::CustomContextMenu); + + // fcnsA = cutterDiff->getFunctionList(true); + // fcnsB = cutterDiff->getFunctionList(false); + // modelA = new FunctionListModel(&fcnsA,this); + // modelB = new FunctionListModel(&fcnsB,this); + + // ui->treeViewFcnsA->setModel(modelA); + // ui->treeViewFcnsB->setModel(modelB); +} + +void CutterDiffWindow::onActionDiffNewFile() +{ + auto loadDiff = new DiffLoadDialog(bDiff, this); + loadDiff->show(); +} diff --git a/src/tools/bindiff/CutterDiffWindow.h b/src/tools/bindiff/CutterDiffWindow.h new file mode 100644 index 0000000000..3bfaa6759a --- /dev/null +++ b/src/tools/bindiff/CutterDiffWindow.h @@ -0,0 +1,139 @@ +#ifndef CUTTERDIFFWINDOW_H +#define CUTTERDIFFWINDOW_H + +#include +#include + +#include +#include +#include +#include + +namespace Ui { +class CutterDiffWindow; +} + +class CutterDiffWindow; + +class DiffMatchModel : public QAbstractListModel +{ + Q_OBJECT + + friend CutterDiffWindow; + +public: + enum Column : ut8 { + NameOrig = 0, + SizeOrig, + AddressOrig, + Similarity, + AddressMod, + SizeMod, + NameMod, + ColumnCount + }; + + DiffMatchModel(QList *list, QColor cPerf, QColor cPart, + QObject *parent = nullptr); + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + QColor gradientByRatio(const double ratio) const; + +private: + QList *list; + + QColor perfect, partial; +}; + +class DiffMismatchModel : public QAbstractListModel +{ + Q_OBJECT + + friend CutterDiffWindow; + +public: + enum Column : ut8 { + FuncName = 0, + FuncAddress, + FuncLinearSize, + FuncNargs, + FuncNlocals, + FuncNbbs, + FuncCalltype, + FuncEdges, + FuncStackframe, + ColumnCount + }; + + DiffMismatchModel(QList *list, QObject *parent = nullptr); + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + +private: + QList *list; +}; + +class FunctionListModel : public AddressableItemModel<> +{ + Q_OBJECT +public: + FunctionListModel(QList *list, QObject *parent = nullptr); + ~FunctionListModel() {} + enum Column : ut8 { Name, Offset, ColumnCount }; + 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; + RVA address(const QModelIndex &index) const override; + void refreshModel(); + +private: + QList *list; +}; + +class CutterDiffWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit CutterDiffWindow(QWidget *parent = nullptr); + ~CutterDiffWindow(); + +public slots: + void onBinDiffCompleted(); + void onActionDiffNewFile(); + +private: + Ui::CutterDiffWindow *ui; + CutterDiff *cutterDiff; + BinDiff *bDiff; + DiffMatchModel *matches; + DiffMismatchModel *added; + DiffMismatchModel *removed; + QList listMatch; + QList listDel; + QList listAdd; + FunctionListModel *modelA; + FunctionListModel *modelB; + QList fcnsA; + QList fcnsB; + void setupFonts(); + void refreshHex(RVA addr); +}; + +#endif // CUTTERDIFFWINDOW_H diff --git a/src/tools/bindiff/CutterDiffWindow.ui b/src/tools/bindiff/CutterDiffWindow.ui new file mode 100644 index 0000000000..3228d19e84 --- /dev/null +++ b/src/tools/bindiff/CutterDiffWindow.ui @@ -0,0 +1,174 @@ + + + CutterDiffWindow + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + + + 300 + 0 + + + + + 0 + 0 + + + + Qt::Orientation::Horizontal + + + false + + + + + 100 + 200 + + + + Qt::Orientation::Vertical + + + + + + + Functions A + + + + + + + + + + + + + + + + + + Functions B + + + + + + + + + + + + + + + + 3 + + + + Match Results + + + + + + + + + + Added + + + + + + + + + + Removed + + + + + + + + + + Page + + + + + + + + + + + + + + + + 0 + 0 + 800 + 22 + + + + + File + + + + + + + + + Diff New Files + + + + + + CutterTreeView + QTreeView +
widgets/CutterTreeView.h
+ 1 +
+ + HexDiff + QWidget +
tools/bindiff/HexDiff.h
+ 1 +
+
+ + +
diff --git a/src/tools/bindiff/DiffLoadDialog.cpp b/src/tools/bindiff/DiffLoadDialog.cpp index 92b755920a..8ebc636f18 100644 --- a/src/tools/bindiff/DiffLoadDialog.cpp +++ b/src/tools/bindiff/DiffLoadDialog.cpp @@ -2,13 +2,16 @@ #include "ui_DiffLoadDialog.h" +#include #include +#include #include #include #include -DiffLoadDialog::DiffLoadDialog(QWidget *parent) : QDialog(parent), ui(new Ui::DiffLoadDialog) +DiffLoadDialog::DiffLoadDialog(BinDiff *bDiff, QWidget *parent) + : QDialog(parent), bDiff(bDiff), ui(new Ui::DiffLoadDialog) { ui->setupUi(this); setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); @@ -32,6 +35,7 @@ DiffLoadDialog::DiffLoadDialog(QWidget *parent) : QDialog(parent), ui(new Ui::Di &DiffLoadDialog::onButtonFileAOpenClicked); connect(ui->buttonFileBOpen, &QPushButton::clicked, this, &DiffLoadDialog::onButtonFileBOpenClicked); + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &DiffLoadDialog::onButtonBoxAccepted); auto index = ui->comboBoxAnalysis->findData(tr("Auto"), Qt::DisplayRole); ui->comboBoxAnalysis->setCurrentIndex(index); @@ -75,6 +79,14 @@ void DiffLoadDialog::onButtonFileAOpenClicked() return; } + const QFileInfo info(fileName); + + if (!info.exists() || !info.isFile()) { + QMessageBox::warning(this, tr("Invalid Path"), + tr("Given file path for File A is not valid.")); + return; + } + ui->lineEditFileB->setText(fileName); } @@ -93,13 +105,31 @@ void DiffLoadDialog::onButtonFileBOpenClicked() if (fileName.isEmpty()) { return; } + const QFileInfo info(fileName); + + if (!info.exists() || !info.isFile()) { + QMessageBox::warning(this, tr("Invalid Path"), + tr("Given file path for File B is not valid.")); + return; + } ui->lineEditFileA->setText(fileName); } void DiffLoadDialog::onButtonBoxAccepted() { - // Check files exists + if (ui->lineEditFileA->text().isEmpty()) { + QMessageBox::warning(this, tr("Empty FileA"), tr("Select a file for diffing.")); + return; + } + if (ui->lineEditFileB->text().isEmpty()) { + QMessageBox::warning(this, tr("Empty FileB"), tr("Select a file for diffing.")); + return; + } + auto waitDialog = new DiffWaitDialog(bDiff, this); + waitDialog->show(ui->lineEditFileA->text(), ui->lineEditFileB->text(), + ui->comboBoxAnalysis->currentIndex(), ui->comboBoxCompare->currentIndex()); + printf("hello world"); emit startDiffing(); } diff --git a/src/tools/bindiff/DiffLoadDialog.h b/src/tools/bindiff/DiffLoadDialog.h index a3aa748494..a9fd1091fb 100644 --- a/src/tools/bindiff/DiffLoadDialog.h +++ b/src/tools/bindiff/DiffLoadDialog.h @@ -1,9 +1,12 @@ #ifndef DIFF_LOAD_DIALOG_H #define DIFF_LOAD_DIALOG_H +#include "DiffWaitDialog.h" + #include #include +#include #include #include @@ -16,7 +19,7 @@ class DiffLoadDialog : public QDialog Q_OBJECT public: - explicit DiffLoadDialog(QWidget *parent = nullptr); + explicit DiffLoadDialog(BinDiff *bDiff, QWidget *parent = nullptr); ~DiffLoadDialog(); QString getFileA() const; @@ -35,6 +38,7 @@ private slots: private: std::unique_ptr ui; + BinDiff *bDiff; }; #endif // DIFF_LOAD_DIALOG_H diff --git a/src/tools/bindiff/DiffWaitDialog.cpp b/src/tools/bindiff/DiffWaitDialog.cpp new file mode 100644 index 0000000000..4134f820a0 --- /dev/null +++ b/src/tools/bindiff/DiffWaitDialog.cpp @@ -0,0 +1,109 @@ +#include "DiffWaitDialog.h" + +#include "ui_DiffWaitDialog.h" + +#include + +#include +#include + +DiffWaitDialog::DiffWaitDialog(BinDiff *bDiff, QWidget *parent) + : QDialog(parent), bDiff(bDiff), timer(parent), ui(new Ui::DiffWaitDialog) +{ + ui->setupUi(this); + setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); + setModal(true); + + ui->lineEditNFuncs->setReadOnly(true); + ui->lineEditMatches->setReadOnly(true); + ui->lineEditOriginal->setReadOnly(true); + ui->lineEditModified->setReadOnly(true); + ui->progressBar->setValue(0); + ui->lineEditNFuncs->setText("0"); + ui->lineEditMatches->setText("0"); + + QTime zero(0, 0, 0, 0); + ui->lineEditElapsedTime->setText(zero.toString("hh:mm:ss")); + ui->lineEditEstimatedTime->setText(zero.toString("hh:mm:ss")); +} + +DiffWaitDialog::~DiffWaitDialog() +{ + if (bDiff && bDiff->isRunning()) { + bDiff->cancel(); + bDiff->wait(); + } + delete bDiff; +} + +void DiffWaitDialog::show(QString original, QString modified, int level, int compare) +{ + connect(this, &DiffWaitDialog::cancelJob, bDiff, &BinDiff::cancel); + connect(bDiff, &BinDiff::progress, this, &DiffWaitDialog::onProgress); + connect(bDiff, &BinDiff::complete, this, &DiffWaitDialog::onCompletion); + connect(&timer, &QTimer::timeout, this, &DiffWaitDialog::updateElapsedTime); + + ui->lineEditOriginal->setText(original); + ui->lineEditModified->setText(modified); + + bDiff->setAnalysisLevel(level); + bDiff->setCompareLogic(compare); + bDiff->setFileB(modified); + bDiff->setFileA(original); + eTimer.restart(); + timer.setSingleShot(false); + timer.start(1000); + + bDiff->start(); + qInfo() << "started"; + this->QDialog::show(); +} + +void DiffWaitDialog::onProgress(BinDiffStatusDescription status) +{ + int partial = status.total - status.nLeft; + ut32 progress = (100 * partial) / status.total; + ui->progressBar->setValue(progress); + ui->lineEditNFuncs->setText(QString::asprintf("%lu", status.nLeft)); + ui->lineEditMatches->setText(QString::asprintf("%lu", status.nMatch)); + + double speed = ((double)partial) / ((double)eTimer.elapsed()); + ut64 seconds = (((double)status.nLeft) / speed) / 1000ull; + int hours = seconds / 3600; + seconds -= (hours * 3600); + int minutes = seconds / 60; + seconds = seconds % 60; + QTime estimated(hours, minutes, seconds, 0); + ui->lineEditEstimatedTime->setText(estimated.toString("hh:mm:ss")); +} + +void DiffWaitDialog::onCompletion() +{ + timer.stop(); + + // if (bDiff->hasData()) { + // auto results = new DiffWindow(bDiff, this); + // bDiff = nullptr; + // results->showMaximized(); + // } + + close(); +} + +void DiffWaitDialog::updateElapsedTime() +{ + ut64 seconds = eTimer.elapsed() / 1000ull; + int hours = seconds / 3600; + seconds -= (hours * 3600); + int minutes = seconds / 60; + seconds = seconds % 60; + QTime current(hours, minutes, seconds, 0); + ui->lineEditElapsedTime->setText(current.toString("hh:mm:ss")); +} + +void DiffWaitDialog::onButtonBoxRejected() +{ + timer.stop(); + emit cancelJob(); + close(); +} diff --git a/src/tools/bindiff/DiffWaitDialog.h b/src/tools/bindiff/DiffWaitDialog.h new file mode 100644 index 0000000000..0993785a22 --- /dev/null +++ b/src/tools/bindiff/DiffWaitDialog.h @@ -0,0 +1,46 @@ +#ifndef DIFF_WAIT_DIALOG_H +#define DIFF_WAIT_DIALOG_H + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Ui { +class DiffWaitDialog; +} + +class DiffWaitDialog : public QDialog +{ + Q_OBJECT + +public: + explicit DiffWaitDialog(BinDiff *bDiff, QWidget *parent = nullptr); + ~DiffWaitDialog(); + + void show(QString original, QString modified, int level, int compare); + +public slots: + void onProgress(BinDiffStatusDescription status); + void onCompletion(); + void updateElapsedTime(); + +signals: + void cancelJob(); + +private slots: + void onButtonBoxRejected(); + +private: + QElapsedTimer eTimer; + QTimer timer; + BinDiff *bDiff; + std::unique_ptr ui; +}; + +#endif // DIFF_WAIT_DIALOG_H diff --git a/src/tools/bindiff/DiffWaitDialog.ui b/src/tools/bindiff/DiffWaitDialog.ui new file mode 100644 index 0000000000..9d86f65a38 --- /dev/null +++ b/src/tools/bindiff/DiffWaitDialog.ui @@ -0,0 +1,151 @@ + + + DiffWaitDialog + + + Qt::NonModal + + + + 0 + 0 + 524 + 342 + + + + + 0 + 0 + + + + Performing binary diffing + + + + QLayout::SetMinimumSize + + + + + QLayout::SetMinimumSize + + + + + 24 + + + + + + + + + + + + + + + Elapsed Time + + + + + + + Modified File + + + + + + + Matches + + + + + + + + + + Original File + + + + + + + + + + Functions left to compare + + + + + + + + + + Estimated Time + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel + + + + + + + + + Remove item + + + + + Remove all + + + Remove all + + + + + + + buttonBox + rejected() + DiffWaitDialog + reject() + + + 316 + 240 + + + 286 + 254 + + + + + diff --git a/src/tools/bindiff/HexDiff.cpp b/src/tools/bindiff/HexDiff.cpp new file mode 100644 index 0000000000..96b0afec42 --- /dev/null +++ b/src/tools/bindiff/HexDiff.cpp @@ -0,0 +1,1931 @@ +#include "HexDiff.h" + +#include "Configuration.h" +#include "Cutter.h" +#include "dialogs/CommentsDialog.h" +#include "dialogs/FlagDialog.h" +#include "dialogs/MarkDialog.h" +#include "dialogs/WriteCommandsDialogs.h" +#include "shortcuts/ShortcutManager.h" +#include "widgets/AddressRangeScrollBar.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +constexpr uint64_t maxCopySize = 128 * 1024 * 1024; +constexpr int maxLineWidthPreset = 32; +constexpr int maxLineWidthBytes = 128 * 1024; +constexpr int warningTimeMs = 500; +} + +HexDiff::HexDiff(QWidget *parent) + : QScrollArea(parent), + cursorEnabled(true), + cursorArea(DiffArea::ItemA), + updatingSelection(false), + itemByteLen(1), + itemGroupSize(1), + rowSizeBytes(16), + columnMode(ColumnMode::PowerOf2), + itemFormat(ItemFormatHex), + itemBigEndian(false), + addrCharLen(AddrWidth64), + showHeader(true), + showAscii(true), + showExHex(true), + showExAddr(true), + warningTimer(this), + vScrollBar(new AddressRangeScrollBar(this)) +{ + setMouseTracking(true); + setFocusPolicy(Qt::FocusPolicy::StrongFocus); + connect(horizontalScrollBar(), &QScrollBar::valueChanged, this, &HexDiff::updateViewport); + + connect(Config(), &Configuration::colorsUpdated, this, &HexDiff::updateColors); + connect(Config(), &Configuration::fontsUpdated, this, + [this]() { setMonospaceFont(Config()->getFont()); }); + + setVerticalScrollBar(vScrollBar); + vScrollBar->setPageStep(10); + vScrollBar->setSingleStep(1); + connect(vScrollBar, &AddressRangeScrollBar::scrolled, this, + [this](int lines) { scrollLines(lines, true); }); + connect(vScrollBar, &QScrollBar::valueChanged, this, + [this](int) { setStartAddress(vScrollBar->address()); }); + connect(vScrollBar, &AddressRangeScrollBar::hideScrollBar, this, + [this]() { setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); }); + connect(vScrollBar, &AddressRangeScrollBar::showScrollBar, this, + [this]() { setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); }); + vScrollBar->refreshRange(); + + auto sizeActionGroup = new QActionGroup(this); + for (int i = 1; i <= 8; i *= 2) { + auto *action = new QAction(QString::number(i), this); + action->setCheckable(true); + action->setActionGroup(sizeActionGroup); + connect(action, &QAction::triggered, this, [=, this]() { setItemSize(i); }); + actionsItemSize.append(action); + } + actionsItemSize.at(0)->setChecked(true); + + /* Follow the order in ItemFormat enum */ + QStringList names; + names << tr("Hexadecimal"); + names << tr("Octal"); + names << tr("Decimal"); + names << tr("Signed decimal"); + names << tr("Float"); + + auto formatActionGroup = new QActionGroup(this); + for (int i = 0; i < names.length(); ++i) { + auto *action = new QAction(names.at(i), this); + action->setCheckable(true); + action->setActionGroup(formatActionGroup); + connect(action, &QAction::triggered, this, + [=, this]() { setItemFormat(static_cast(i)); }); + actionsItemFormat.append(action); + } + actionsItemFormat.at(0)->setChecked(true); + actionsItemFormat.at(ItemFormatFloat)->setEnabled(false); + + rowSizeMenu = new QMenu(tr("Bytes per row"), this); + auto columnsActionGroup = new QActionGroup(this); + for (int i = 1; i <= maxLineWidthPreset; i *= 2) { + auto *action = new QAction(QString::number(i), rowSizeMenu); + action->setCheckable(true); + action->setActionGroup(columnsActionGroup); + connect(action, &QAction::triggered, this, [=, this]() { setFixedLineSize(i); }); + rowSizeMenu->addAction(action); + } + rowSizeMenu->addSeparator(); + actionRowSizePowerOf2 = new QAction(tr("Power of 2"), this); + actionRowSizePowerOf2->setCheckable(true); + actionRowSizePowerOf2->setActionGroup(columnsActionGroup); + connect(actionRowSizePowerOf2, &QAction::triggered, this, + [=, this]() { setColumnMode(ColumnMode::PowerOf2); }); + rowSizeMenu->addAction(actionRowSizePowerOf2); + + actionItemBigEndian = new QAction(tr("Big Endian"), this); + actionItemBigEndian->setCheckable(true); + actionItemBigEndian->setEnabled(false); + connect(actionItemBigEndian, &QAction::triggered, this, &HexDiff::setItemEndianness); + + actionHexPairs = new QAction(tr("Bytes as pairs"), this); + actionHexPairs->setCheckable(true); + connect(actionHexPairs, &QAction::triggered, this, &HexDiff::onHexPairsModeEnabled); + + actionCopy = Shortcuts()->makeAction("Hex.copy", this); + addAction(actionCopy); + actionCopy->setShortcutContext(Qt::ShortcutContext::WidgetWithChildrenShortcut); + connect(actionCopy, &QAction::triggered, this, &HexDiff::copy); + + actionCopyAddress = Shortcuts()->makeAction("General.copyAddress", this); + actionCopyAddress->setShortcutContext(Qt::ShortcutContext::WidgetWithChildrenShortcut); + connect(actionCopyAddress, &QAction::triggered, this, &HexDiff::copyAddress); + addAction(actionCopyAddress); + + actionSelectRange = new QAction(tr("Select range"), this); + connect(actionSelectRange, &QAction::triggered, this, + [this]() { rangeDialog.openAt(cursor.address); }); + addAction(actionSelectRange); + connect(&rangeDialog, &QDialog::accepted, this, &HexDiff::onRangeDialogAccepted); + + connect(this, &HexDiff::selectionChanged, this, + [this](Selection newSelection) { actionCopy->setEnabled(!newSelection.empty); }); + + updateMetrics(); + updateItemLength(); + + startAddress = 0ULL; + cursor.address = 0ULL; + ctxA.data.reset(new MemoryData()); + ctxB.data.reset(new MemoryData()); + ctxA.file = DiffFile::A; + ctxB.file = DiffFile::B; + + fetchData(); + updateCursorMeta(); + + connect(&cursor.blinkTimer, &QTimer::timeout, this, &HexDiff::onCursorBlinked); + cursor.setBlinkPeriod(1000); + cursor.startBlinking(); + + updateColors(); + + warningTimer.setSingleShot(true); + connect(&warningTimer, &QTimer::timeout, this, &HexDiff::hideWarningRect); +} + +void HexDiff::setMonospaceFont(const QFont &font) +{ + if (!(font.styleHint() & QFont::Monospace)) { + /* FIXME: Use default monospace font + setFont(XXX); */ + } + QScrollArea::setFont(font); + monospaceFont = font.resolve(this->font()); + updateMetrics(); + fetchData(); + updateCursorMeta(); + + updateViewport(); +} + +void HexDiff::setItemSize(int nbytes) +{ + static const QVector values({ 1, 2, 4, 8 }); + + if (!values.contains(nbytes)) { + return; + } + + itemByteLen = nbytes; + if (itemByteLen > rowSizeBytes) { + rowSizeBytes = itemByteLen; + } + + actionsItemFormat.at(ItemFormatFloat)->setEnabled(nbytes >= 4); + actionItemBigEndian->setEnabled(nbytes != 1); + + updateItemLength(); + if (cursorArea > 1 && cursor.address % itemByteLen) { + moveCursor(-int(cursor.address % itemByteLen)); + } + fetchData(); + updateCursorMeta(); + + updateViewport(); +} + +void HexDiff::setItemFormat(ItemFormat format) +{ + + itemFormat = format; + + bool sizeEnabled = true; + if (format == ItemFormatFloat) { + sizeEnabled = false; + } + actionsItemSize.at(0)->setEnabled(sizeEnabled); + actionsItemSize.at(1)->setEnabled(sizeEnabled); + + updateItemLength(); + fetchData(); + updateCursorMeta(); + + updateViewport(); +} + +void HexDiff::setItemGroupSize(int size) +{ + itemGroupSize = size; + + updateCounts(); + fetchData(); + updateCursorMeta(); + + updateViewport(); +} + +void HexDiff::updateCounts() +{ + actionHexPairs->setEnabled(rowSizeBytes > 1 && itemByteLen == 1 + && itemFormat == ItemFormat::ItemFormatHex); + actionHexPairs->setChecked(Core()->getConfigb("hex.pairs")); + if (actionHexPairs->isChecked() && actionHexPairs->isEnabled()) { + itemGroupSize = 2; + } else { + itemGroupSize = 1; + } + + if (columnMode == ColumnMode::PowerOf2) { + int lastGoodSize = itemGroupByteLen(); + for (int i = itemGroupByteLen(); i <= maxLineWidthBytes; i *= 2) { + rowSizeBytes = i; + itemColumns = rowSizeBytes / itemGroupByteLen(); + updateAreasPosition(); + if (horizontalScrollBar()->maximum() == 0) { + lastGoodSize = rowSizeBytes; + } else { + break; + } + } + rowSizeBytes = lastGoodSize; + } + + itemColumns = rowSizeBytes / itemGroupByteLen(); + + // ensure correct action is selected when changing line size programmatically + if (columnMode == ColumnMode::Fixed) { + int w = 1; + const auto &actions = rowSizeMenu->actions(); + for (auto action : actions) { + action->setChecked(false); + } + for (auto action : actions) { + if (w > maxLineWidthPreset) { + break; + } + if (rowSizeBytes == w) { + action->setChecked(true); + } + w *= 2; + } + } else if (columnMode == ColumnMode::PowerOf2) { + actionRowSizePowerOf2->setChecked(true); + } + + updateAreasPosition(); +} + +void HexDiff::setFixedLineSize(int lineSize) +{ + if (lineSize < 1 || lineSize < itemGroupByteLen() || lineSize % itemGroupByteLen()) { + updateCounts(); + return; + } + rowSizeBytes = lineSize; + columnMode = ColumnMode::Fixed; + + updateCounts(); + fetchData(); + updateCursorMeta(); + + updateViewport(); +} + +void HexDiff::setColumnMode(ColumnMode mode) +{ + columnMode = mode; + + updateCounts(); + fetchData(); + updateCursorMeta(); + + updateViewport(); +} + +void HexDiff::selectRange(RVA start, RVA end) +{ + BasicDiffCursor endCursor(end); + endCursor += 1; + setCursorAddr(endCursor); + selection.set(start, end); + cursorEnabled = false; + emit selectionChanged(getSelection()); +} + +void HexDiff::clearSelection() +{ + setCursorAddr(BasicDiffCursor(cursor.address), false); + emit selectionChanged(getSelection()); +} + +HexDiff::Selection HexDiff::getSelection() +{ + return Selection { selection.isEmpty(), selection.start(), selection.end() }; +} + +void HexDiff::seek(uint64_t address) +{ + if (cursorArea > 1) { + // when other widget causes seek to the middle of word + // switch to ascii column which operates with byte positions + auto viewOffset = startAddress % itemByteLen; + auto addrOffset = address % itemByteLen; + if ((addrOffset + itemByteLen - viewOffset) % itemByteLen) { + setCursorOnAscii(true); + } + } + setCursorAddr(BasicDiffCursor(address)); +} + +void HexDiff::refresh() +{ + fetchData(); + updateViewport(); +} + +void HexDiff::setItemEndianness(bool bigEndian) +{ + itemBigEndian = bigEndian; + + updateCursorMeta(); // Update cached item character + + updateViewport(); +} + +void HexDiff::updateColors() +{ + borderColor = Config()->getColor("gui.border"); + backgroundColor = Config()->getColor("gui.background"); + b0x00Color = Config()->getColor("b0x00"); + b0x7fColor = Config()->getColor("b0x7f"); + b0xffColor = Config()->getColor("b0xff"); + printableColor = Config()->getColor("ai.write"); + defColor = Config()->getColor("btext"); + addrColor = Config()->getColor("func_var_addr"); + diffColor = Config()->getColor("graph.diff.unmatch"); + warningColor = QColor("red"); + + updateCursorMeta(); + updateViewport(); +} + +void HexDiff::paintEvent(QPaintEvent *event) +{ + QPainter painter(viewport()); + painter.setFont(monospaceFont); + + const int xOffset = horizontalScrollBar()->value(); + if (xOffset > 0) { + painter.translate(QPoint(-xOffset, 0)); + } + + const QRect cursorRectA = cursor.screenPos.toAlignedRect(); + const QRect cursorRectB = cursor.screenPosB.toAlignedRect(); + if (event->rect() == cursorRectA || event->rect() == cursorRectB) { + /*cursor blink*/ + drawCursor(painter); + return; + } + + painter.fillRect(event->rect().translated(xOffset, 0), backgroundColor); + + drawHeader(painter, ctxA); + drawHeader(painter, ctxB); + + drawAddrArea(painter, ctxA); + drawItemArea(painter, ctxA); + drawAsciiArea(painter, ctxA); + + drawItemArea(painter, ctxB); + drawAsciiArea(painter, ctxB); + drawAddrArea(painter, ctxB); + + if (warningRectVisible) { + painter.setPen(warningColor); + painter.drawRect(warningRect); + } + + if (!cursorEnabled) { + return; + } + + drawCursor(painter, true); +} + +void HexDiff::updateWidth() +{ + int max = (showAscii ? ctxB.asciiArea.right() : ctxB.itemArea.right()) - viewport()->width(); + if (max < 0) { + max = 0; + } else { + max += charWidth; + } + horizontalScrollBar()->setMaximum(max); + horizontalScrollBar()->setSingleStep(charWidth); +} + +bool HexDiff::isFixedWidth() const +{ + return itemFormat == ItemFormatHex || itemFormat == ItemFormatOct; +} + +void HexDiff::resizeEvent(QResizeEvent *event) +{ + const int oldByteCount = bytesPerScreen(); + updateCounts(); + + if (event->oldSize().height() == event->size().height() && oldByteCount == bytesPerScreen()) { + return; + } + + updateAreasHeight(); + fetchData(); // rowCount was changed + updateCursorMeta(); + + updateViewport(); +} + +DiffFile HexDiff::getFileFromPos(QPoint &pos) +{ + if (ctxA.asciiArea.contains(pos) || ctxA.itemArea.contains(pos)) { + return DiffFile::A; + } + return DiffFile::B; +} + +void HexDiff::mouseMoveEvent(QMouseEvent *event) +{ + QPoint pos = event->pos(); + pos.rx() += horizontalScrollBar()->value(); + + const bool cursorOnArea = ctxA.asciiArea.contains(pos) || ctxB.asciiArea.contains(pos) + || ctxA.itemArea.contains(pos) || ctxB.itemArea.contains(pos); + + auto mouseAddr = mousePosToAddrA(pos).address; + + QString infoText; + if (!updatingSelection && (cursorOnArea)) { + QString metaData = getFileFromPos(pos) == DiffFile::A + ? getFlagsAndComment(mouseAddr, ctxA) + : getFlagsAndComment(getAddressB(mouseAddr), ctxB); // has to be redefined with ctx + if (!metaData.isEmpty() && (ctxA.itemArea.contains(pos) || ctxB.itemArea.contains(pos))) { + infoText = metaData.replace(",", ", "); + } + + const auto marks = Core()->getMarksAt(mouseAddr); + for (const auto &mark : marks) { + if (mark.realname.isEmpty()) { + continue; + } + if (!infoText.isEmpty()) { + infoText += "
"; + } + const QColor c = mark.color; + infoText += QString("● " + " %5") + .arg(c.red()) + .arg(c.green()) + .arg(c.blue()) + .arg(markAlphaF) + .arg(mark.realname.toHtmlEscaped()); + } + if (!infoText.isEmpty()) { + // forces tooltip to follow cursor movement + QToolTip::showText(mapToGlobal(event->pos()), infoText + " ", this); + + QToolTip::showText(mapToGlobal(event->pos()), infoText, this); + } else { + QToolTip::hideText(); + } + } else { + QToolTip::hideText(); + } + + if (!updatingSelection) { + if (cursorOnArea) { + setCursor(Qt::IBeamCursor); + } else { + setCursor(Qt::ArrowCursor); + } + return; + } + + auto &area = currentArea(); + if (pos.x() < area.left()) { + pos.setX(area.left()); + } else if (pos.x() > area.right()) { + pos.setX(area.right()); + } + auto addr = currentAreaPosToAddrA(pos, true); + setCursorAddr(addr, true); + + /* Stop blinking */ + cursorEnabled = false; + + updateViewport(); +} + +const QRectF &HexDiff::currentArea() +{ + switch (cursorArea) { + case DiffArea::AsciiA: + return ctxA.asciiArea; + case DiffArea::AsciiB: + return ctxB.asciiArea; + case DiffArea::ItemA: + return ctxA.itemArea; + case DiffArea::ItemB: + return ctxB.itemArea; + } + return ctxA.itemArea; +} + +DiffArea HexDiff::posToDiffArea(QPoint &point) const +{ + if (ctxB.itemArea.contains(point)) { + return DiffArea::ItemB; + } else if (ctxA.asciiArea.contains(point)) { + return DiffArea::AsciiA; + } else if (ctxB.asciiArea.contains(point)) { + return DiffArea::AsciiB; + } + return DiffArea::ItemA; +} + +bool HexDiff::diffItemsAt(uint64_t addrA) +{ + quint8 a[8]; + quint8 b[8]; + ctxA.data->copy(a, addrA, static_cast(itemByteLen)); + ctxB.data->copy(b, getAddressB(addrA), static_cast(itemByteLen)); + return memcmp(a, b, itemByteLen); +} + +bool HexDiff::diffByteArrays(QByteArray &a, QByteArray &b) +{ + return a == b; +} + +void HexDiff::mousePressEvent(QMouseEvent *event) +{ + QPoint pos(event->pos()); + pos.rx() += horizontalScrollBar()->value(); + + if (event->button() == Qt::LeftButton) { + const DiffArea area = posToDiffArea(pos); + const bool selectingData = area > DiffArea::AsciiB; + const bool selecting = selectingData || area <= DiffArea::AsciiB; + const bool holdingShift = event->modifiers() == Qt::ShiftModifier; + + if (selecting) { + updatingSelection = true; + setCursorOnArea(area); + auto cursorPosition = currentAreaPosToAddrA(pos, true); + setCursorAddr(cursorPosition, holdingShift); + updateViewport(); + } + } +} + +void HexDiff::mouseDoubleClickEvent(QMouseEvent *event) +{ + QPoint pos(event->pos()); + pos.rx() += horizontalScrollBar()->value(); +} + +void HexDiff::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + if (selection.isEmpty()) { + selection.init(BasicDiffCursor(cursor.address)); + cursorEnabled = true; + updateViewport(); + } + updatingSelection = false; + } +} + +void HexDiff::wheelEvent(QWheelEvent *event) +{ + + // according to Qt doc 1 row per 5 degrees, angle measured in 1/8 of degree + const int dy = event->angleDelta().y() / (8 * 5); + scrollLines(dy); + vScrollBar->showTransientScrollBar(); +} + +HexDiff::HexNavigationMode HexDiff::defaultNavigationMode() +{ + return HexNavigationMode::Words; +} + +bool HexDiff::event(QEvent *event) +{ + // prefer treating keys like 's' 'g' '.' as typing input instead of global shortcuts + if (event->type() == QEvent::ShortcutOverride) { + auto keyEvent = static_cast(event); + auto modifiers = keyEvent->modifiers(); + if ((modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier + || modifiers == Qt::KeypadModifier) + && keyEvent->key() < Qt::Key_Escape) { + keyEvent->accept(); + return true; + } + } + + return QScrollArea::event(event); +} + +void HexDiff::keyPressEvent(QKeyEvent *event) +{ + bool select = false; + auto moveOrSelect = [event, &select](QKeySequence::StandardKey moveSeq, + QKeySequence::StandardKey selectSeq) -> bool { + if (event->matches(moveSeq)) { + select = false; + return true; + } else if (event->matches(selectSeq)) { + select = true; + return true; + } + return false; + }; + + if (cursorArea < 2 || navigationMode == HexNavigationMode::Words + || navigationMode == HexNavigationMode::AnyChar) { + if (moveOrSelect(QKeySequence::MoveToNextPage, QKeySequence::SelectNextPage)) { + moveCursor(bytesPerScreen(), select); + } else if (moveOrSelect(QKeySequence::MoveToPreviousPage, + QKeySequence::SelectPreviousPage)) { + moveCursor(-bytesPerScreen(), select); + } else if (moveOrSelect(QKeySequence::MoveToStartOfLine, QKeySequence::SelectStartOfLine)) { + const int linePos = + int((cursor.address % itemRowByteLen()) - (startAddress % itemRowByteLen())); + moveCursor(-linePos, select); + } else if (moveOrSelect(QKeySequence::MoveToEndOfLine, QKeySequence::SelectEndOfLine)) { + const int linePos = + int((cursor.address % itemRowByteLen()) - (startAddress % itemRowByteLen())); + moveCursor(itemRowByteLen() - linePos, select); + } + } + + if (navigationMode == HexNavigationMode::Words || cursorArea < 2) { + if (moveOrSelect(QKeySequence::MoveToNextLine, QKeySequence::SelectNextLine)) { + moveCursor(itemRowByteLen(), select, OverflowMove::Ignore); + } else if (moveOrSelect(QKeySequence::MoveToPreviousLine, + QKeySequence::SelectPreviousLine)) { + moveCursor(-itemRowByteLen(), select, OverflowMove::Ignore); + } else if (moveOrSelect(QKeySequence::MoveToNextChar, QKeySequence::SelectNextChar) + || moveOrSelect(QKeySequence::MoveToNextWord, QKeySequence::SelectNextWord)) { + moveCursor(cursorArea < 2 ? 1 : itemByteLen, select); + } else if (moveOrSelect(QKeySequence::MoveToPreviousChar, QKeySequence::SelectPreviousChar) + || moveOrSelect(QKeySequence::MoveToPreviousWord, + QKeySequence::SelectPreviousWord)) { + moveCursor(cursorArea < 2 ? -1 : -itemByteLen, select); + } + } else if (navigationMode == HexNavigationMode::AnyChar && cursorArea < 1) { + if (moveOrSelect(QKeySequence::MoveToNextChar, QKeySequence::SelectNextChar)) { + if (select) { + moveCursor(itemByteLen, select); + } else { + if (!selection.isEmpty()) { + clearSelection(); + } + } + updateViewport(); + } else if (event->matches(QKeySequence::SelectPreviousChar)) { + moveCursor(-itemByteLen, true); + } else if (moveOrSelect(QKeySequence::MoveToNextWord, QKeySequence::SelectNextWord)) { + moveCursor(itemByteLen, select); + } else if (event->matches(QKeySequence::MoveToPreviousWord)) { + moveCursor(-itemByteLen, false); + } else if (event->matches(QKeySequence::SelectPreviousWord)) { + moveCursor(-itemByteLen, true); + } + } +} + +void HexDiff::contextMenuEvent(QContextMenuEvent *event) +{ + const QPoint pt = event->pos(); + bool mouseOutsideSelection = false; + if (event->reason() == QContextMenuEvent::Mouse) { + auto mouseAddr = mousePosToAddrA(pt).address; + if (ctxA.asciiArea.contains(pt)) { + cursorArea = DiffArea::AsciiA; + } else if (ctxB.asciiArea.contains(pt)) { + cursorArea = DiffArea::AsciiB; + } else if (ctxB.itemArea.contains(pt)) { + cursorArea = DiffArea::ItemB; + } else { + cursorArea = DiffArea::ItemA; + } + if (selection.isEmpty()) { + seek(mouseAddr); + } else { + mouseOutsideSelection = !selection.contains(mouseAddr); + } + } + + auto disableOutsideSelectionActions = [this](bool disable) { + actionCopyAddress->setDisabled(disable); + }; + + auto *menu = new QMenu(this); + QMenu *sizeMenu = menu->addMenu(tr("Item size:")); + sizeMenu->addActions(actionsItemSize); + QMenu *formatMenu = menu->addMenu(tr("Item format:")); + formatMenu->addActions(actionsItemFormat); + menu->addMenu(rowSizeMenu); + menu->addAction(actionHexPairs); + menu->addAction(actionItemBigEndian); + menu->addSeparator(); + menu->addAction(actionCopy); + disableOutsideSelectionActions(mouseOutsideSelection); + menu->addAction(actionCopyAddress); + menu->addActions(this->actions()); + + menu->exec(mapToGlobal(pt)); + disableOutsideSelectionActions(false); + menu->deleteLater(); +} + +void HexDiff::onCursorBlinked() +{ + if (!cursorEnabled) { + return; + } + cursor.blink(); + const int xOffset = horizontalScrollBar()->value(); + viewport()->update(cursor.screenPos.toAlignedRect().translated(-xOffset, 0)); + viewport()->update(cursor.screenPosB.toAlignedRect().translated(-xOffset, 0)); +} + +void HexDiff::onHexPairsModeEnabled(bool enable) +{ + // Sync configuration + Core()->setConfig("hex.pairs", enable); + if (enable) { + setItemGroupSize(2); + } else { + setItemGroupSize(1); + } +} + +void HexDiff::copy() +{ // needs to be changed double cores + if (selection.isEmpty() || selection.size() > maxCopySize) { + return; + } + + auto x = cursorArea < 2 + ? Core()->getString(selection.start(), selection.size(), RZ_STRING_ENC_8BIT, true) + : Core()->ioRead(selection.start(), (int)selection.size()).toHex(); + QApplication::clipboard()->setText(x); +} + +void HexDiff::copyAddress() +{ + const uint64_t addr = getLocationAddress(); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(rzAddressString(addr)); +} + +void HexDiff::onRangeDialogAccepted() +{ + if (rangeDialog.empty()) { + seek(rangeDialog.getStartAddress()); + return; + } + selectRange(rangeDialog.getStartAddress(), rangeDialog.getEndAddress()); +} + +void HexDiff::updateItemLength() +{ + itemPrefixLen = 0; + itemPrefix.clear(); + + switch (itemFormat) { + case ItemFormatHex: + itemCharLen = 2 * itemByteLen; + if (itemByteLen > 1 && showExHex) { + itemPrefixLen = hexPrefix.length(); + itemPrefix = hexPrefix; + } + break; + case ItemFormatOct: + itemCharLen = (itemByteLen * 8 + 3) / 3; + break; + case ItemFormatDec: + switch (itemByteLen) { + case 1: + itemCharLen = 3; + break; + case 2: + itemCharLen = 5; + break; + case 4: + itemCharLen = 10; + break; + case 8: + itemCharLen = 20; + break; + } + break; + case ItemFormatSignedDec: + switch (itemByteLen) { + case 1: + itemCharLen = 4; + break; + case 2: + itemCharLen = 6; + break; + case 4: + itemCharLen = 11; + break; + case 8: + itemCharLen = 20; + break; + } + break; + case ItemFormatFloat: + if (itemByteLen < 4) { + itemByteLen = 4; + } + // FIXME + itemCharLen = 3 * itemByteLen; + break; + } + + itemCharLen += itemPrefixLen; + + updateCounts(); +} + +void HexDiff::drawHeader(QPainter &painter, DiffFileContext &ctx) +{ + if (!showHeader) { + return; + } + + int offset = 0; + QRectF rect(ctx.itemArea.left(), 0, itemWidth(), lineHeight); + + painter.setPen(addrColor); + + for (int j = 0; j < itemColumns; ++j) { + for (int k = 0; k < itemGroupSize; ++k, offset += itemByteLen) { + painter.drawText(rect, Qt::AlignVCenter | Qt::AlignRight, + QString::number(offset, 16).toUpper()); + rect.translate(itemWidth(), 0); + } + rect.translate(columnSpacingWidth(), 0); + } + + rect.moveLeft(ctx.asciiArea.left()); + rect.setWidth(charWidth); + for (int j = 0; j < itemRowByteLen(); ++j) { + painter.drawText(rect, Qt::AlignVCenter | Qt::AlignRight, + QString::number(j % 16, 16).toUpper()); + rect.translate(charWidth, 0); + } +} + +void HexDiff::drawCursor(QPainter &painter, bool shadow) +{ + if (shadow) { + QPen pen(Qt::gray); + pen.setStyle(Qt::DashLine); + painter.setPen(pen); + qreal shadowWidth = charWidth; + if (cursorArea < 2) { + shadowWidth = itemWidth(); + } + shadowCursor.screenPos.setWidth(shadowWidth); + shadowCursor.screenPosB.setWidth(shadowWidth); + painter.drawRect(shadowCursor.screenPos); + painter.drawRect(shadowCursor.screenPosB); + painter.setPen(Qt::SolidLine); + } + + painter.setPen(cursor.cachedColor); + QRectF charRect(cursor.screenPos); + charRect.setWidth(charWidth); + painter.fillRect(charRect, backgroundColor); + painter.drawText(charRect, Qt::AlignVCenter, cursor.cachedChar); + painter.setPen(cursor.cachedColorB); + QRectF charRectB(cursor.screenPosB); + charRectB.setWidth(charWidth); + painter.fillRect(charRectB, backgroundColor); + painter.drawText(charRectB, Qt::AlignVCenter, cursor.cachedCharB); + if (cursor.isVisible) { + painter.setCompositionMode(QPainter::RasterOp_SourceXorDestination); + painter.fillRect(cursor.screenPos, QColor(0xff, 0xff, 0xff)); + painter.fillRect(cursor.screenPosB, QColor(0xff, 0xff, 0xff)); + } +} + +void HexDiff::drawAddrArea(QPainter &painter, DiffFileContext &ctx) +{ + + uint64_t offset = ctx.file == DiffFile::A ? startAddress : getStartAddressB(); + + QString addrString; + const QSizeF areaSize((addrCharLen + (showExAddr ? 2 : 0)) * charWidth, lineHeight); + QRectF strRect(ctx.addrArea.topLeft(), areaSize); + + painter.setPen(addrColor); + for (int line = 0; line < visibleLines && offset <= ctx.data->maxIndex(); + ++line, strRect.translate(0, lineHeight), offset += itemRowByteLen()) { + addrString = QString("%1").arg(offset, addrCharLen, 16, QLatin1Char('0')); + if (showExAddr) { + addrString.prepend(hexPrefix); + } + painter.drawText(strRect, Qt::AlignVCenter, addrString); + } + + painter.setPen(borderColor); + + const qreal vLineOffset = ctx.itemArea.left() - charWidth; + painter.drawLine(QLineF(vLineOffset, 0, vLineOffset, viewport()->height())); +} + +uint64_t HexDiff::ctxAddr(uint64_t addrA, DiffFileContext &ctx) +{ + if (ctxA.file == DiffFile::A) { + return getAddressB(addrA); + } + return addrA; +} + +HexDiffSelection HexDiff::ctxSelection(DiffFileContext &ctx) +{ + if (ctx.file == DiffFile::A) { + return selection; + } + HexDiffSelection selectionB; + if (!selection.isEmpty()) { + selectionB.set(getAddressB(selection.start()), getAddressB(selection.end())); + } + return selectionB; +} + +void HexDiff::drawItemArea(QPainter &painter, DiffFileContext &ctx) +{ + const uint64_t addr = ctx.file == DiffFile::A ? startAddress : getStartAddressB(); + + QRectF itemRect(ctx.itemArea.topLeft(), QSizeF(itemWidth(), lineHeight)); + QColor itemColor; + QString itemString; + + fillSelectionBackground(painter, ctx); + + const HexDiffSelection &ctxSel = ctxSelection(ctx); + + uint64_t itemAddr = addr; + auto &itemCursor = cursorArea < 2 ? shadowCursor : cursor; + for (int line = 0; line < visibleLines; ++line) { + itemRect.moveLeft(ctx.itemArea.left()); + for (int j = 0; j < itemColumns; ++j) { + for (int k = 0; k < itemGroupSize && itemAddr <= ctx.data->maxIndex(); + ++k, itemAddr += itemByteLen) { + + itemString = renderItem(itemAddr - addr, ctx, &itemColor); + + if (!getFlagsAndComment(itemAddr, ctx).isEmpty()) { + QColor markerColor(borderColor); + markerColor.setAlphaF(0.5); + painter.setPen(markerColor); + for (const auto &shape : rangePolygons(itemAddr, itemAddr, false, ctx)) { + painter.drawPolyline(shape); + } + } + if (ctxSel.contains(itemAddr) && cursorArea > 1) { + itemColor = palette().highlightedText().color(); + } + + painter.setPen(itemColor); + painter.drawText(itemRect, Qt::AlignVCenter, itemString); + itemRect.translate(itemWidth(), 0); + if (ctx.file == DiffFile::A && itemAddr == cursor.address) { + itemCursor.cachedChar = itemString.at(0); + itemCursor.cachedColor = itemColor; + } + if (ctx.file == DiffFile::B && itemAddr == getAddressB(cursor.address)) { + itemCursor.cachedCharB = itemString.at(0); + itemCursor.cachedColorB = itemColor; + } + } + itemRect.translate(columnSpacingWidth(), 0); + } + itemRect.translate(0, lineHeight); + } + + painter.setPen(borderColor); + + const qreal vLineOffset = ctx.asciiArea.left() - charWidth; + painter.drawLine(QLineF(vLineOffset, 0, vLineOffset, viewport()->height())); +} + +void HexDiff::drawAsciiArea(QPainter &painter, DiffFileContext &ctx) +{ + const uint64_t addr = ctx.file == DiffFile::A ? startAddress : getStartAddressB(); + QRectF charRect(ctx.asciiArea.topLeft(), QSizeF(charWidth, lineHeight)); + + fillSelectionBackground(painter, ctx, true); + painter.setBrush(Qt::NoBrush); + + uint64_t address = addr; + QChar ascii; + QColor color; + auto &itemCursor = cursorArea < 2 ? cursor : shadowCursor; + for (int line = 0; line < visibleLines; ++line, charRect.translate(0, lineHeight)) { + charRect.moveLeft(ctx.asciiArea.left()); + for (int j = 0; j < itemRowByteLen() && address <= ctx.data->maxIndex(); ++j, ++address) { + ascii = renderAscii(address - addr, ctx, &color); + if (selection.contains(address) && cursorArea < 2) { + color = palette().highlightedText().color(); + } + painter.setPen(color); + /* Dots look ugly. Use fillRect() instead of drawText(). */ + if (ascii == '.') { + const qreal a = cursor.screenPos.width(); + QPointF p = charRect.bottomLeft(); + p.rx() += (charWidth - a) / 2 + 1; + p.ry() += -2 * a; + painter.fillRect(QRectF(p, QSizeF(a, a)), color); + } else { + painter.drawText(charRect, Qt::AlignVCenter, ascii); + } + charRect.translate(charWidth, 0); + if (ctx.file == DiffFile::A && cursor.address == address) { + itemCursor.cachedChar = ascii; + itemCursor.cachedColor = color; + } + if (ctx.file == DiffFile::B && getAddressB(cursor.address) == address) { + itemCursor.cachedCharB = ascii; + itemCursor.cachedColorB = color; + } + } + } +} + +void HexDiff::fillSelectionBackground(QPainter &painter, DiffFileContext &ctx, bool ascii) +{ + if (selection.isEmpty()) { + return; + } + const auto parts = + rangePolygons(ctxSelection(ctx).start(), ctxSelection(ctx).end(), ascii, ctx); + for (const auto &shape : parts) { + const QColor highlightColor = palette().color(QPalette::Highlight); + if (ascii == cursorArea < 2) { + painter.setBrush(highlightColor); + painter.drawPolygon(shape); + } else { + painter.setPen(highlightColor); + painter.drawPolyline(shape); + } + } +} + +QVector HexDiff::rangePolygons(RVA start, RVA last, bool ascii, DiffFileContext &ctx) +{ + const uint64_t addr = ctx.file == DiffFile::A ? startAddress : getStartAddressB(); + + if (last < addr || start > lastVisibleAddr(ctx.file)) { + return {}; + } + + QRectF rect; + const QRectF area = QRectF(ascii ? ctx.asciiArea : ctx.itemArea); + + /* Convert absolute values to relative */ + const int startOffset = std::max(uint64_t(start), addr) - addr; + const int endOffset = std::min(uint64_t(last), lastVisibleAddr(ctx.file)) - addr; + + QVector parts; + + auto getRectangle = [&](int offset) { + return QRectF(ascii ? asciiRectangle(offset, ctx) : itemRectangle(offset, ctx)); + }; + + auto startRect = getRectangle(startOffset); + auto endRect = getRectangle(endOffset); + bool startJagged = false; + bool endJagged = false; + if (!ascii) { + if (const int startFraction = startOffset % itemByteLen) { + startRect.setLeft(startRect.left() + startFraction * startRect.width() / itemByteLen); + startJagged = true; + } + if (const int endFraction = itemByteLen - 1 - (endOffset % itemByteLen)) { + endRect.setRight(endRect.right() - endFraction * endRect.width() / itemByteLen); + endJagged = true; + } + } + if (endOffset - startOffset + 1 <= rowSizeBytes) { + if (startOffset / rowSizeBytes == endOffset / rowSizeBytes) { // single row + rect = startRect; + rect.setRight(endRect.right()); + parts.push_back(QPolygonF(rect)); + } else { + // two separate rectangles + rect = startRect; + rect.setRight(area.right()); + parts.push_back(QPolygonF(rect)); + rect = endRect; + rect.setLeft(area.left()); + parts.push_back(QPolygonF(rect)); + } + } else { + // single multiline shape + QPolygonF shape; + shape << startRect.topLeft(); + rect = getRectangle(startOffset + rowSizeBytes - 1 - startOffset % rowSizeBytes); + shape << rect.topRight(); + if (endOffset % rowSizeBytes != rowSizeBytes - 1) { + rect = getRectangle(endOffset - endOffset % rowSizeBytes - 1); + shape << rect.bottomRight() << endRect.topRight(); + } + shape << endRect.bottomRight(); + shape << getRectangle(endOffset - endOffset % rowSizeBytes).bottomLeft(); + if (startOffset % rowSizeBytes) { + rect = getRectangle(startOffset - startOffset % rowSizeBytes + rowSizeBytes); + shape << rect.topLeft() << startRect.bottomLeft(); + } + shape << shape.first(); // close the shape + parts.push_back(shape); + } + if (!ascii && (startJagged || endJagged) && parts.length() >= 1) { + + QPolygonF top; + top.reserve(3); + top << QPointF(0, 0) << QPointF(charWidth, lineHeight / 3) << QPointF(0, lineHeight / 2); + QPolygonF bottom; + bottom.reserve(3); + bottom << QPointF(0, lineHeight / 2) << QPointF(-charWidth, 2 * lineHeight / 3) + << QPointF(0, lineHeight); + + // small adjustment to make sure that edges don't overlap with rect edges, QPolygonF doesn't + // handle it properly + const QPointF adjustment(charWidth / 16, 0); + top.translate(-adjustment); + bottom.translate(adjustment); + + if (startJagged) { + auto movedTop = top.translated(startRect.topLeft()); + auto movedBottom = bottom.translated(startRect.topLeft()); + parts[0] = parts[0].subtracted(movedTop).united(movedBottom); + } + if (endJagged) { + auto movedTop = top.translated(endRect.topRight()); + auto movedBottom = bottom.translated(endRect.topRight()); + parts.last() = parts.last().subtracted(movedBottom).united(movedTop); + } + } + return parts; +} + +void HexDiff::updateMetrics() +{ + const QFontMetricsF fontMetrics(this->monospaceFont); + lineHeight = fontMetrics.height(); +#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0) + charWidth = fontMetrics.width('A'); +#else + charWidth = fontMetrics.horizontalAdvance('A'); +#endif + + updateCounts(); + updateAreasHeight(); + + const qreal cursorWidth = std::max(charWidth / 3, 1.); + cursor.screenPos.setHeight(lineHeight); + shadowCursor.screenPos.setHeight(lineHeight); + + cursor.screenPos.setWidth(cursorWidth); + + cursor.screenPosB.setHeight(lineHeight); + shadowCursor.screenPosB.setHeight(lineHeight); + + cursor.screenPosB.setWidth(cursorWidth); + if (cursorArea < 2) { + cursor.screenPos.moveTopLeft(ctxA.asciiArea.topLeft()); + + shadowCursor.screenPos.setWidth(itemWidth()); + shadowCursor.screenPos.moveTopLeft(ctxA.itemArea.topLeft()); + + cursor.screenPosB.moveTopLeft(ctxB.asciiArea.topLeft()); + + shadowCursor.screenPosB.setWidth(itemWidth()); + shadowCursor.screenPosB.moveTopLeft(ctxB.itemArea.topLeft()); + } else { + cursor.screenPos.moveTopLeft(ctxA.itemArea.topLeft()); + shadowCursor.screenPos.setWidth(charWidth); + shadowCursor.screenPos.moveTopLeft(ctxA.asciiArea.topLeft()); + + cursor.screenPosB.moveTopLeft(ctxB.itemArea.topLeft()); + shadowCursor.screenPosB.setWidth(charWidth); + shadowCursor.screenPosB.moveTopLeft(ctxB.asciiArea.topLeft()); + } +} + +void HexDiff::updateAreasPosition() +{ + const qreal spacingWidth = areaSpacingWidth(); + + const qreal yOffset = showHeader ? lineHeight : 0; + + ctxA.addrArea.setTopLeft(QPointF(0, yOffset)); + ctxA.addrArea.setWidth((addrCharLen + (showExAddr ? 2 : 0)) * charWidth); + + ctxA.itemArea.setTopLeft(QPointF(ctxA.addrArea.right() + spacingWidth, yOffset)); + ctxA.itemArea.setWidth(itemRowWidth()); + + ctxA.asciiArea.setTopLeft(QPointF(ctxA.itemArea.right() + spacingWidth, yOffset)); + ctxA.asciiArea.setWidth(asciiRowWidth()); + + ctxB.addrArea.setTopLeft(QPointF(ctxA.asciiArea.right() + spacingWidth, yOffset)); + ctxB.addrArea.setWidth((addrCharLen + (showExAddr ? 2 : 0)) * charWidth); + + ctxB.itemArea.setTopLeft(QPointF(ctxB.addrArea.right() + spacingWidth, yOffset)); + ctxB.itemArea.setWidth(itemRowWidth()); + + ctxB.asciiArea.setTopLeft(QPointF(ctxB.itemArea.right() + spacingWidth, yOffset)); + ctxB.asciiArea.setWidth(asciiRowWidth()); + + updateWidth(); +} + +void HexDiff::updateAreasHeight() +{ + visibleLines = static_cast((viewport()->height() - ctxA.itemArea.top()) / lineHeight); + + const qreal height = visibleLines * lineHeight; + ctxA.addrArea.setHeight(height); + ctxA.itemArea.setHeight(height); + ctxA.asciiArea.setHeight(height); + ctxB.addrArea.setHeight(height); + ctxB.itemArea.setHeight(height); + ctxB.asciiArea.setHeight(height); +} + +bool HexDiff::moveCursor(int offset, bool select, OverflowMove overflowMove) +{ + const uint64_t maxIndex = qMin(ctxA.data->maxIndex(), ctxB.data->maxIndex()); + BasicDiffCursor addr(cursor.address); + if (overflowMove == OverflowMove::Ignore) { + if (addr.moveChecked(offset)) { + if (addr.address > maxIndex) { + addr.address = maxIndex; + addr.pastEnd = true; + } + setCursorAddr(addr, select); + return true; + } + return false; + } else { + addr += offset; + if (addr.address > maxIndex) { + addr.address = maxIndex; + } + setCursorAddr(addr, select); + return true; + } +} + +void HexDiff::setCursorAddr(BasicDiffCursor addr, bool select) +{ + if (!select) { + const bool clearingSelection = !selection.isEmpty(); + selection.init(addr); + if (clearingSelection) { + emit selectionChanged(getSelection()); + } + } + emit positionChanged(addr.address); + + cursor.address = addr.address; + if (cursorArea > 1) { + cursor.address -= cursor.address % itemByteLen; + } + + /* Pause cursor repainting */ + cursorEnabled = false; + + if (select) { + selection.update(addr); + emit selectionChanged(getSelection()); + } + + uint64_t addressValue = cursor.address; + /* Update data cache if necessary */ + if (!(addressValue >= startAddress && addressValue <= lastVisibleAddr(DiffFile::A))) { + /* Align start address */ + addressValue -= (addressValue % itemRowByteLen()); + + /* FIXME: handling Page Up/Down */ + const uint64_t rowAfterVisibleAddress = startAddress + bytesPerScreen(); + if (addressValue == rowAfterVisibleAddress && addressValue > startAddress) { + // when pressing down add only one new row + startAddress += itemRowByteLen(); + } else { + startAddress = addressValue; + } + + fetchData(); + + if (startAddress > (ctxA.data->maxIndex() - bytesPerScreen()) + 1) { + startAddress = (ctxA.data->maxIndex() - bytesPerScreen()) + 1; + } + } + + updateCursorMeta(); + + /* Draw cursor */ + cursor.isVisible = !select; + updateViewport(); + + /* Resume cursor repainting */ + cursorEnabled = selection.isEmpty(); +} + +void HexDiff::updateCursorMeta() +{ + QPointF point; + QPointF pointAscii; + + const int offset = cursor.address - startAddress; + int itemOffset = offset; + int asciiOffset; + + /* Calc common Y coordinate */ + point.ry() = (itemOffset / itemRowByteLen()) * lineHeight; + pointAscii.setY(point.y()); + itemOffset %= itemRowByteLen(); + asciiOffset = itemOffset; + + /* Calc X coordinate on the item area */ + point.rx() = (itemOffset / itemGroupByteLen()) * columnExWidth(); + itemOffset %= itemGroupByteLen(); + point.rx() += (itemOffset / itemByteLen) * itemWidth(); + + /* Calc X coordinate on the ascii area */ + pointAscii.rx() = asciiOffset * charWidth; + + QPointF pointB = point; + QPointF pointAsciiB = pointAscii; + + point += ctxA.itemArea.topLeft(); + pointAscii += ctxA.asciiArea.topLeft(); + pointB += ctxB.itemArea.topLeft(); + pointAsciiB += ctxB.asciiArea.topLeft(); + + cursor.screenPos.moveTopLeft(cursorArea < 2 ? pointAscii : point); + shadowCursor.screenPos.moveTopLeft(cursorArea < 2 ? point : pointAscii); + cursor.screenPosB.moveTopLeft(cursorArea < 2 ? pointAsciiB : pointB); + shadowCursor.screenPosB.moveTopLeft(cursorArea < 2 ? pointB : pointAsciiB); +} + +void HexDiff::setCursorOnAscii(bool ascii) +{ + if (cursorArea % 2) { + if (ascii) { + cursorArea = DiffArea::AsciiB; + } else { + cursorArea = DiffArea::ItemB; + } + } else { + if (ascii) { + cursorArea = DiffArea::AsciiB; + } else { + cursorArea = DiffArea::ItemB; + } + } +} + +void HexDiff::setCursorOnArea(DiffArea area) +{ + cursorArea = area; +} + +QColor HexDiff::itemColor(uint8_t byte) +{ + QColor color(defColor); + + if (byte == 0x00) { + color = b0x00Color; + } else if (byte == 0x7f) { + color = b0x7fColor; + } else if (byte == 0xff) { + color = b0xffColor; + } else if (IS_PRINTABLE(byte)) { + color = printableColor; + } + + return color; +} + +template +static T fromBigEndian(const void *src) +{ +#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) + return qFromBigEndian(src); +#else + T result; + memcpy(&result, src, sizeof(T)); + return qFromBigEndian(result); +#endif +} + +template +static T fromLittleEndian(const void *src) +{ +#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) + return qFromLittleEndian(src); +#else + T result; + memcpy(&result, src, sizeof(T)); + return qFromLittleEndian(result); +#endif +} + +QVariant HexDiff::readItem(int offset, DiffFileContext &ctx, QColor *color) +{ + const uint64_t addr = ctx.file == DiffFile::A ? startAddress : getStartAddressB(); + quint8 byte; + quint16 word; + quint32 dword; + quint64 qword; + float float32; + double float64; + + quint8 bytes[sizeof(uint64_t)]; + ctx.data->copy(bytes, addr + offset, static_cast(itemByteLen)); + const bool signedItem = itemFormat == ItemFormatSignedDec; + + if (color) { + *color = defColor; + } + + switch (itemByteLen) { + case 1: + byte = bytes[0]; + if (color) { + *color = itemColor(byte); + } + if (!signedItem) { + return QVariant(static_cast(byte)); + } + return QVariant(static_cast(static_cast(byte))); + case 2: + if (itemBigEndian) { + word = fromBigEndian(bytes); + } else { + word = fromLittleEndian(bytes); + } + + if (!signedItem) { + return QVariant(static_cast(word)); + } + return QVariant(static_cast(static_cast(word))); + case 4: + if (itemBigEndian) { + dword = fromBigEndian(bytes); + } else { + dword = fromLittleEndian(bytes); + } + + if (itemFormat == ItemFormatFloat) { + memcpy(&float32, &dword, sizeof(float32)); + return QVariant(float32); + } + if (!signedItem) { + return QVariant(static_cast(dword)); + } + return QVariant(static_cast(static_cast(dword))); + case 8: + if (itemBigEndian) { + qword = fromBigEndian(bytes); + } else { + qword = fromLittleEndian(bytes); + } + if (itemFormat == ItemFormatFloat) { + memcpy(&float64, &qword, sizeof(float64)); + return QVariant(float64); + } + if (!signedItem) { + return QVariant(qword); + } + return QVariant(static_cast(qword)); + } + + return QVariant(); +} + +QString HexDiff::renderItem(int offset, DiffFileContext &ctx, QColor *color) +{ + QString item; + const QVariant itemVal = readItem(offset, ctx, color); + const int itemLen = itemCharLen - itemPrefixLen; /* Reserve space for prefix */ + + // FIXME: handle broken itemVal ( QVariant() ) + switch (itemFormat) { + case ItemFormatHex: + item = QString("%1").arg(itemVal.toULongLong(), itemLen, 16, QLatin1Char('0')); + if (itemByteLen > 1 && showExHex) { + item.prepend(hexPrefix); + } + break; + case ItemFormatOct: + item = QString("%1").arg(itemVal.toULongLong(), itemLen, 8, QLatin1Char('0')); + break; + case ItemFormatDec: + item = QString("%1").arg(itemVal.toULongLong(), itemLen, 10); + break; + case ItemFormatSignedDec: + item = QString("%1").arg(itemVal.toLongLong(), itemLen, 10); + break; + case ItemFormatFloat: + item = QString("%1").arg(itemVal.toDouble(), itemLen, 'g', itemByteLen == 4 ? 6 : 15); + break; + } + + return item; +} + +QChar HexDiff::renderAscii(int offset, DiffFileContext &ctx, QColor *color) +{ + const uint64_t addr = ctx.file == DiffFile::A ? startAddress : getStartAddressB(); + uchar byte; + ctx.data->copy(&byte, addr + offset, sizeof(byte)); + if (color) { + *color = itemColor(byte); + } + if (!IS_PRINTABLE(byte)) { + byte = '.'; + } + return QChar(byte); +} + +/** + * @brief Gets the available flags and comment at a specific address. + * @param address Address of Item to be checked. + * @return String containing the flags and comment available at the address. + */ +QString HexDiff::getFlagsAndComment(uint64_t address, DiffFileContext &ctx) +{ // Needs to be redefined using BinDiffClass so that it can be used for both cores + const QString flagNames = Core()->listFlagsAsStringAt(address); + QString metaData = flagNames.isEmpty() ? "" : "Flags: " + flagNames.trimmed(); + + const QString comment = Core()->getCommentAt(address); + if (!comment.isEmpty()) { + if (!metaData.isEmpty()) { + metaData.append("\n"); + } + metaData.append("Comment: " + comment.trimmed()); + } + + return metaData; +} + +template +static bool checkRange(BigValue v) +{ + return v >= std::numeric_limits::min() && v <= std::numeric_limits::max(); +} + +template +static bool checkAndWrite(BigInteger value, uint8_t *buf, bool littleEndian) +{ + if (!checkRange(value)) { + return false; + } + if (littleEndian) { + qToLittleEndian((T)value, buf); + } else { + qToBigEndian((T)value, buf); + } + return true; +} + +template +static bool checkAndWriteWithSign(const QVariant &value, uint8_t *buf, bool isSigned, + bool littleEndian) +{ + if (isSigned) { + return checkAndWrite(value.toLongLong(), buf, littleEndian); + } else { + return checkAndWrite(value.toULongLong(), buf, littleEndian); + } +} + +bool HexDiff::parseWord(const QString &word, uint8_t *buf, size_t bufferSize) const +{ + bool parseOk = false; + if (bufferSize < size_t(itemByteLen)) { + return false; + } + if (itemFormat == ItemFormatFloat) { + if (itemByteLen == 4) { + const float value = word.toFloat(&parseOk); + if (!parseOk) { + return false; + } + if (itemBigEndian) { + rz_write_be_float(buf, value); + } else { + rz_write_le_float(buf, value); + } + return true; + } else if (itemByteLen == 8) { + const double value = word.toDouble(&parseOk); + if (!parseOk) { + return false; + } + if (itemBigEndian) { + rz_write_be_double(buf, value); + } else { + rz_write_le_double(buf, value); + } + return true; + } + return false; + } else { + QVariant value; + bool isSigned = false; + switch (itemFormat) { + case ItemFormatHex: + value = word.toULongLong(&parseOk, 16); + break; + case ItemFormatOct: + value = word.toULongLong(&parseOk, 8); + break; + case ItemFormatDec: + value = word.toULongLong(&parseOk, 10); + break; + case ItemFormatSignedDec: + isSigned = true; + value = word.toLongLong(&parseOk, 10); + break; + default: + break; + } + if (!parseOk) { + return false; + } + + switch (itemByteLen) { + case 1: + return checkAndWriteWithSign(value, buf, isSigned, !itemBigEndian); + case 2: + return checkAndWriteWithSign(value, buf, isSigned, !itemBigEndian); + case 4: + return checkAndWriteWithSign(value, buf, isSigned, !itemBigEndian); + case 8: + return checkAndWriteWithSign(value, buf, isSigned, !itemBigEndian); + } + } + return false; +} + +void HexDiff::fetchData() +{ + ctxA.data->fetch(startAddress, bytesPerScreen()); + ctxB.data->fetch(getStartAddressB(), bytesPerScreen()); +} + +const QRectF &HexDiff::screenPosToArea(const QPoint &point) const +{ + if (ctxA.itemArea.contains(point)) { + return ctxA.itemArea; + } else if (ctxA.asciiArea.contains(point)) { + return ctxA.asciiArea; + } else if (ctxB.itemArea.contains(point)) { + return ctxB.itemArea; + } else if (ctxB.asciiArea.contains(point)) { + return ctxB.asciiArea; + } + return ctxA.asciiArea; +} + +BasicDiffCursor HexDiff::screenPosToAddrA(const QPoint &point, bool middle, int *wordOffset) const +{ + QPointF pt = point - screenPosToArea(point).topLeft(); + + int relativeAddress = 0; + const int line = static_cast(pt.y() / lineHeight); + relativeAddress += line * itemRowByteLen(); + const int column = static_cast(pt.x() / columnExWidth()); + relativeAddress += column * itemGroupByteLen(); + pt.rx() -= column * columnExWidth(); + auto roundingOffset = middle ? itemWidth() / 2 : 0; + int posInGroup = static_cast((pt.x() + roundingOffset) / itemWidth()); + if (!middle) { + posInGroup = std::min(posInGroup, itemGroupSize - 1); + } + relativeAddress += posInGroup * itemByteLen; + pt.rx() -= posInGroup * itemWidth(); + BasicDiffCursor result(startAddress); + result += relativeAddress; + + if (!middle && wordOffset != nullptr) { + int charPos = static_cast((pt.x() / charWidth) + 0.5); + charPos -= itemPrefixLen; + charPos = std::max(0, charPos); + *wordOffset = charPos; + } + return result; +} + +BasicDiffCursor HexDiff::asciiPosToAddrA(const QPoint &point, bool middle) const +{ + const QPointF pt = point - screenPosToArea(point).topLeft(); + + int relativeAddress = 0; + relativeAddress += static_cast(pt.y() / lineHeight) * itemRowByteLen(); + auto roundingOffset = middle ? (charWidth / 2) : 0; + relativeAddress += static_cast((pt.x() + (roundingOffset)) / charWidth); + BasicDiffCursor result(startAddress); + result += relativeAddress; + + return result; +} + +BasicDiffCursor HexDiff::currentAreaPosToAddrA(const QPoint &point, bool middle) const +{ + return cursorArea < 2 ? asciiPosToAddrA(point, middle) : screenPosToAddrA(point, middle); +} + +BasicDiffCursor HexDiff::mousePosToAddrA(const QPoint &point, bool middle) const +{ + return (ctxA.asciiArea.contains(point) || ctxB.asciiArea.contains(point)) + ? asciiPosToAddrA(point, middle) + : screenPosToAddrA(point, middle); +} + +QRectF HexDiff::itemRectangle(int offset, DiffFileContext &ctx) +{ + qreal x; + qreal y; + + qreal width = itemWidth(); + y = (offset / itemRowByteLen()) * lineHeight; + offset %= itemRowByteLen(); + + x = (offset / itemGroupByteLen()) * columnExWidth(); + offset %= itemGroupByteLen(); + x += (offset / itemByteLen) * itemWidth(); + if (offset == 0) { + x -= charWidth / 2; + width += charWidth / 2; + } + if (static_cast(offset) == itemGroupByteLen() - 1) { + width += charWidth / 2; + } + + x += ctx.itemArea.x(); + y += ctx.itemArea.y(); + + return QRectF(x, y, width, lineHeight); +} + +QRectF HexDiff::asciiRectangle(int offset, DiffFileContext &ctx) +{ + QPointF p; + + p.ry() = (offset / itemRowByteLen()) * lineHeight; + offset %= itemRowByteLen(); + + p.rx() = offset * charWidth; + + p += ctx.asciiArea.topLeft(); + + return QRectF(p, QSizeF(charWidth, lineHeight)); +} + +RVA HexDiff::getLocationAddress() +{ + return !selection.isEmpty() ? selection.start() : cursor.address; +} + +void HexDiff::hideWarningRect() +{ + warningRectVisible = false; + updateViewport(); +} + +void HexDiff::showWarningRect(QRectF rect) +{ + warningRect = rect; + warningRectVisible = true; + warningTimer.start(warningTimeMs); + updateViewport(); +} + +void HexDiff::updateViewport() +{ + vScrollBar->setPosition(startAddress); + viewport()->update(); +} + +void HexDiff::scrollLines(int lines, bool clampToScrollBarRange) +{ + const uint64_t maxIndex = qMin(ctxA.data->maxIndex(), ctxB.data->maxIndex()); + const int64_t delta = -lines * itemRowByteLen(); + + if (lines == 0) { + return; + } + + if (delta < 0 && startAddress < static_cast(-delta)) { + startAddress = 0; + } else if (delta > 0 && maxIndex < static_cast(bytesPerScreen())) { + startAddress = 0; + } else if ((maxIndex - startAddress) <= static_cast(bytesPerScreen() + delta - 1)) { + startAddress = (maxIndex - bytesPerScreen()) + 1; + } else { + startAddress += delta; + } + + if (clampToScrollBarRange) { + startAddress = vScrollBar->clampAddressToRange(startAddress); + } + fetchData(); + + updateCursorStatus(); + updateViewport(); +} + +void HexDiff::setStartAddress(RVA address) +{ + RVA aligned = address - (address % itemByteLen); + + const uint64_t maxIdx = qMin(ctxA.data->maxIndex(), ctxB.data->maxIndex()); + const uint64_t screenBytes = bytesPerScreen(); + if (maxIdx > screenBytes) { + RVA maxStart = (maxIdx - screenBytes + 1); + maxStart -= (maxStart % itemByteLen); + aligned = std::min(aligned, maxStart); + } else { + aligned = 0; + } + + // if (aligned == startAddress) { + // return; + // } + startAddress = aligned; + fetchData(); + + updateCursorStatus(); + updateViewport(); +} + +void HexDiff::shiftStartAddress(int shift) +{ + if (shift < 0) { + setStartAddress(startAddress - qAbs(shift)); + return; + } + setStartAddress(startAddress + shift); +} + +void HexDiff::transpose(int transA, int transB) +{ + relTranspose += transB * itemByteLen - transA * itemByteLen; + shiftStartAddress(transA * itemByteLen); + clearSelection(); + moveCursor(-transB); +} + +void HexDiff::updateCursorStatus() +{ + if (cursor.address >= startAddress && cursor.address <= lastVisibleAddr(DiffFile::A)) { + /* Don't enable cursor blinking if selection isn't empty */ + cursorEnabled = selection.isEmpty(); + updateCursorMeta(); + } else { + cursorEnabled = false; + } +} diff --git a/src/tools/bindiff/HexDiff.h b/src/tools/bindiff/HexDiff.h new file mode 100644 index 0000000000..097357cd41 --- /dev/null +++ b/src/tools/bindiff/HexDiff.h @@ -0,0 +1,494 @@ +#ifndef HEXDIFF_H +#define HEXDIFF_H + +#include "Cutter.h" +#include "common/IOModesController.h" +#include "dialogs/HexdumpRangeDialog.h" +#include "widgets/HexWidget.h" +// #include "tools/bindiff/HexDiffWidget.h" + +#include +#include +#include + +#include + +// Defining DiffFile Struct for encapsulation +enum DiffFile : ut8 { A, B }; + +enum DiffArea : ut8 { + AsciiA, + AsciiB, + ItemA, + ItemB +}; // used modulo and comparisons for getting areas for cursorArea and area in mouse press event + // both need to be rewritten with a nullarea or no area pointer + +class DiffFileContext +{ +public: + DiffFile file; + uint64_t startAddress; + std::unique_ptr data; + QRectF addrArea; + QRectF itemArea; + QRectF asciiArea; +}; + +/** + * @brief Tracks memory addresses while preventing 64-bit overflow + */ +struct BasicDiffCursor +{ + uint64_t address; + uint64_t addressB; + bool pastEnd; + explicit BasicDiffCursor(uint64_t pos) : address(pos), pastEnd(false) {} + BasicDiffCursor() : address(0), pastEnd(false) {} + BasicDiffCursor &operator+=(int64_t offset) + { + if (offset < 0 && uint64_t(-offset) > address) { + address = 0; + pastEnd = false; + } else if (offset > 0 && uint64_t(offset) > (UINT64_MAX - address)) { + address = UINT64_MAX; + pastEnd = true; + } else { + address += uint64_t(offset); + pastEnd = false; + } + return *this; + } + BasicDiffCursor &operator+=(int offset) + { + *this += int64_t(offset); + return *this; + } + + bool moveChecked(int offset) + { + auto oldAddress = address; + *this += offset; + return address - oldAddress == uint64_t(offset); + } + + BasicDiffCursor &operator+=(uint64_t offset) + { + if (uint64_t(offset) > (UINT64_MAX - address)) { + address = UINT64_MAX; + pastEnd = true; + } else { + address += offset; + pastEnd = false; + } + return *this; + } + bool operator<(const BasicDiffCursor &r) const + { + return address < r.address || (pastEnd < r.pastEnd); + } +}; + +/** + * @brief Controls the cursor's visual blinking and screen location + */ +struct HexDiffCursor +{ + HexDiffCursor() : isVisible(false), onAsciiArea(false) {} + + bool isVisible; + bool onAsciiArea; + QTimer blinkTimer; + QRectF screenPos; + QRectF screenPosB; + uint64_t address; + QString cachedChar; + QString cachedCharB; + QColor cachedColor; + QColor cachedColorB; + + void blink() { isVisible = !isVisible; } + void setBlinkPeriod(int msec) { blinkTimer.setInterval(msec / 2); } + void startBlinking() { blinkTimer.start(); } + void stopBlinking() { blinkTimer.stop(); } +}; + +class HexDiffSelection +{ +public: + HexDiffSelection() : empty(true) { mStart = mEnd = 0; } + + inline void init(BasicDiffCursor addr) + { + empty = true; + mInit = addr; + } + + void set(uint64_t start, uint64_t end) + { + empty = false; + mInit = BasicDiffCursor(start); + mStart = start; + mEnd = end; + } + + void update(BasicDiffCursor addr) + { + empty = false; + if (mInit < addr) { + mStart = mInit.address; + mEnd = addr.address; + if (!addr.pastEnd) { + mEnd -= 1; + } + } else if (addr < mInit) { + mStart = addr.address; + mEnd = mInit.address; + if (!mInit.pastEnd) { + mEnd -= 1; + } + } else { + mStart = mEnd = mInit.address; + empty = true; + } + } + + bool intersects(uint64_t start, uint64_t end) const + { + return !empty && mEnd >= start && mStart <= end; + } + + bool contains(uint64_t pos) const { return !empty && mStart <= pos && pos <= mEnd; } + + uint64_t size() const + { + uint64_t size = 0; + if (!isEmpty()) { + size = mEnd - mStart + 1; + } + return size; + } + + inline bool isEmpty() const { return empty; } + inline uint64_t start() const { return mStart; } + inline uint64_t end() const { return mEnd; } + +private: + BasicDiffCursor mInit; + uint64_t mStart; + uint64_t mEnd; + bool empty; +}; + +class AddressRangeScrollBar; + +/** + * @brief Widget for rendering and editing of hex and ASCII memory data + */ +class HexDiff : public QScrollArea +{ + Q_OBJECT + +public: + explicit HexDiff(QWidget *parent = nullptr); + ~HexDiff() override = default; + + void setMonospaceFont(const QFont &font); + + enum AddrWidth : ut8 { AddrWidth32 = 8, AddrWidth64 = 16 }; + enum ItemSize : ut8 { + ItemSizeByte = 1, + ItemSizeWord = 2, + ItemSizeDword = 4, + ItemSizeQword = 8 + }; + enum ItemFormat : ut8 { + ItemFormatHex, + ItemFormatOct, + ItemFormatDec, + ItemFormatSignedDec, + ItemFormatFloat + }; + + enum class ColumnMode : ut8 { Fixed, PowerOf2 }; + enum class EditWordState : ut8 { Read, WriteNotStarted, WriteNotEdited, WriteEdited }; + enum class HexNavigationMode : ut8 { Words, WordChar, AnyChar }; + + void setItemSize(int nbytes); + void setItemFormat(ItemFormat format); + void setItemEndianness(bool bigEndian); + void setItemGroupSize(int size); + /** + * @brief Sets line size in bytes. + * Changes column mode to fixed. Command can be rejected if current item format is bigger than + * requested size. + * @param bytes line size in bytes. + */ + void setFixedLineSize(int bytes); + void setColumnMode(ColumnMode mode); + + /** + * @brief Select non empty inclusive range [start; end] + * @param start + * @param end + */ + void selectRange(RVA start, RVA end); + void clearSelection(); + + void transpose(int transA = 0, int transB = 1); + void shiftStartAddress(int shift); + + struct Selection + { + bool empty; + RVA startAddress; + RVA endAddress; + }; + Selection getSelection(); +public slots: + void seek(uint64_t address); + void refresh(); + void updateColors(); +signals: + void selectionChanged(HexDiff::Selection selection); + void positionChanged(RVA start); + +protected: + void paintEvent(QPaintEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void wheelEvent(QWheelEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void contextMenuEvent(QContextMenuEvent *event) override; + bool event(QEvent *event) override; + +private slots: + void onCursorBlinked(); + void onHexPairsModeEnabled(bool enable); + void copy(); + void copyAddress(); + void onRangeDialogAccepted(); + +private: + void updateItemLength(); + void updateCounts(); + void drawHeader(QPainter &painter, DiffFileContext &ctx); + void drawCursor(QPainter &painter, bool shadow = false); + void drawAddrArea(QPainter &painter, DiffFileContext &ctx); + void drawItemArea(QPainter &painter, DiffFileContext &ctx); + void drawAsciiArea(QPainter &painter, DiffFileContext &ctx); + void fillSelectionBackground(QPainter &painter, DiffFileContext &ctx, bool ascii = false); + void updateMetrics(); + void updateAreasPosition(); + void updateAreasHeight(); + enum class OverflowMove : ut8 { Clamp, Ignore }; + bool moveCursor(int offset, bool select = false, + OverflowMove overflowMove = + OverflowMove::Clamp); // The entire movecursor has to cursor shall be + // moved in same direction in both do with + // BasicDiffCursor and HexDiffSelection + void moveCursorKeepEditOffset(int byteOffset, bool select, OverflowMove overflowMove); + void setCursorAddr(BasicDiffCursor addr, bool select = false); + void updateCursorMeta(); + void setCursorOnAscii(bool ascii); + void setCursorOnArea(DiffArea area); + QColor itemColor(uint8_t byte); + QVariant readItem(int offset, DiffFileContext &ctx, QColor *color = nullptr); + QString renderItem(int offset, DiffFileContext &ctx, QColor *color = nullptr); + QChar renderAscii(int offset, DiffFileContext &ctx, QColor *color = nullptr); + QString getFlagsAndComment(uint64_t address, DiffFileContext &ctx); + /** + * @brief Get the location on which operations such as Writing should apply. + * @return Start of selection if multiple bytes are selected. Otherwise, the curren seek of the + * widget. + */ + RVA getLocationAddress(); + + void fetchData(); + /** + * @brief Convert mouse position to address. + * @param point mouse position in widget + * @param middle start next position from middle of symbol. Use middle=true for vertical cursor + * position between symbols, middle=false for insert mode cursor and getting symbol under + * cursor. + * @return + */ + BasicDiffCursor screenPosToAddrA(const QPoint &point, bool middle = false, + int *wordOffset = nullptr) const; + BasicDiffCursor asciiPosToAddrA(const QPoint &point, bool middle = false) const; + BasicDiffCursor currentAreaPosToAddrA(const QPoint &point, bool middle = false) const; + BasicDiffCursor mousePosToAddrA(const QPoint &point, bool middle = false) const; + + BasicDiffCursor screenPosToAddrB(const QPoint &point, bool middle = false, + int *wordOffset = nullptr) const; + BasicDiffCursor asciiPosToAddrB(const QPoint &point, bool middle = false) const; + BasicDiffCursor currentAreaPosToAddrB(const QPoint &point, bool middle = false) const; + BasicDiffCursor mousePosToAddrB(const QPoint &point, bool middle = false) const; + DiffArea posToDiffArea(QPoint &point) const; + /** + * @brief Rectangle for single item in data area. + * @param offset relative to first byte on screen + * @return + */ + QRectF itemRectangle(int offset, DiffFileContext &file); + /** + * @brief Rectangle for single item in ascii area. + * @param offset relative to first byte on screen + * @return + */ + QRectF asciiRectangle(int offset, DiffFileContext &ctx); + QVector rangePolygons(RVA start, RVA last, bool ascii, DiffFileContext &ctx); + void updateWidth(); + + inline qreal itemWidth() const { return itemCharLen * charWidth; } + + inline int itemGroupCharLen() const { return itemCharLen * itemGroupSize; } + + inline int columnExCharLen() const { return itemGroupCharLen() + columnSpacing; } + + inline int itemGroupByteLen() const { return itemByteLen * itemGroupSize; } + + inline qreal columnWidth() const { return itemGroupCharLen() * charWidth; } + + inline qreal columnExWidth() const { return columnExCharLen() * charWidth; } + + inline qreal columnSpacingWidth() const { return columnSpacing * charWidth; } + + inline int itemRowCharLen() const { return itemColumns * columnExCharLen() - columnSpacing; } + + inline int itemRowByteLen() const { return rowSizeBytes; } + + inline int bytesPerScreen() const { return itemRowByteLen() * visibleLines; } + + inline qreal itemRowWidth() const { return itemRowCharLen() * charWidth; } + + inline qreal asciiRowWidth() const { return itemRowByteLen() * charWidth; } + + inline qreal areaSpacingWidth() const { return areaSpacing * charWidth; } + + inline uint64_t lastVisibleAddr(DiffFile file) const + { + return ((file == DiffFile::A ? startAddress : getStartAddressB()) - 1) + bytesPerScreen(); + } + + const QRectF ¤tArea(); + + const QRectF &screenPosToArea(const QPoint &point) const; + + bool isFixedWidth() const; + + HexNavigationMode defaultNavigationMode(); + bool parseWord(const QString &word, uint8_t *buf, size_t bufferSize) const; + + void hideWarningRect(); + void showWarningRect(QRectF rect); + + void updateViewport(); + void scrollLines(int lines, bool clampToScrollBarRange = false); + /** + * @brief Sets the given address as the first visible address of the view + * No action is taken if the address is already at the top + * @param address Target RVA to display at the top + */ + void setStartAddress(RVA address); + + uint64_t getAddressB(uint64_t addr) const + { + if (relTranspose < 0) { + return addr - qAbs(relTranspose); + } + return addr + relTranspose; + } + uint64_t getStartAddressB() const { return getAddressB(startAddress); } + + /** + * @brief Updates cursor visibility and metadata + * The cursor is only visible if it is within the visible address range and selection is empty + */ + void updateCursorStatus(); + + bool cursorEnabled; + DiffArea cursorArea; + HexDiffCursor cursor; + HexDiffCursor shadowCursor; + + HexDiffSelection selection; + bool updatingSelection; + + int itemByteLen = 1; + int itemGroupSize = 1; ///< Items per group (default: 1), 2 in case of hexpair mode + int rowSizeBytes = 16; ///< Line size in bytes + int itemColumns = 16; ///< Number of columns, single column consists of itemGroupSize items + int itemCharLen = 2; + int itemPrefixLen = 0; + int relTranspose = 0; ///< relative transpose between the files + ColumnMode columnMode; + + ItemFormat itemFormat; + + bool itemBigEndian; + QString itemPrefix; + + int visibleLines; + uint64_t startAddress; + qreal charWidth; + qreal lineHeight; + int addrCharLen; + QFont monospaceFont; + + bool showHeader; + bool showAscii; + bool showExHex; + bool showExAddr; + + bool diffByteArrays(QByteArray &a, QByteArray &b); + bool diffItemsAt(uint64_t addr); + + QColor borderColor; + QColor backgroundColor; + QColor defColor; + QColor addrColor; + QColor diffColor; + QColor b0x00Color; + QColor b0x7fColor; + QColor b0xffColor; + QColor printableColor; + QColor warningColor; + + HexdumpRangeDialog rangeDialog; + + /* Spacings in characters */ + const int columnSpacing = 1; + const int areaSpacing = 2; + + const QString hexPrefix = QStringLiteral("0x"); + + QMenu *rowSizeMenu; + QAction *actionRowSizePowerOf2; + QList actionsItemSize; + QList actionsItemFormat; + QAction *actionItemBigEndian; + QAction *actionHexPairs; + QAction *actionCopy; + QAction *actionCopyAddress; + QAction *actionSelectRange; + + DiffFileContext ctxA; + DiffFileContext ctxB; + DiffFile getFileFromPos(QPoint &pos); + HexDiffSelection ctxSelection(DiffFileContext &ctx); + uint64_t ctxAddr(uint64_t addrA, DiffFileContext &ctx); + + HexNavigationMode navigationMode = HexNavigationMode::Words; + + bool warningRectVisible = false; + QRectF warningRect; + QTimer warningTimer; + + AddressRangeScrollBar *vScrollBar; +}; + +#endif // HEXWIDGET_H From ad730c5f24d11074d4bfe87833a99e51d8548b58 Mon Sep 17 00:00:00 2001 From: NewtronReal Date: Sat, 20 Jun 2026 12:46:23 +0530 Subject: [PATCH 05/13] Cutter Diff clang format --- src/CMakeLists.txt | 4 +- src/core/BinDiff.cpp | 8 ++ src/core/CutterDiff.cpp | 237 ++++++++++++++++++++++++++++++++++++++++ src/core/CutterDiff.h | 49 +++++++++ src/core/MainWindow.cpp | 8 ++ 5 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 src/core/CutterDiff.cpp create mode 100644 src/core/CutterDiff.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6c7c3bca1f..8421c58645 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -530,7 +530,9 @@ endif() set(CUTTER_SOURCES ${OPTIONS} ${UI_FILES} ${QRC_FILES} ${PLATFORM_RESOURCES} ${SOURCES} ${HEADER_FILES}) source_group(TREE "${CMAKE_CURRENT_LIST_DIR}" FILES ${CUTTER_SOURCES}) -add_executable(Cutter ${CUTTER_SOURCES} ${BINDINGS_SOURCE}) +add_executable(Cutter ${CUTTER_SOURCES} ${BINDINGS_SOURCE} + tools/bindiff/CutterDiffWindow.h tools/bindiff/CutterDiffWindow.cpp tools/bindiff/CutterDiffWindow.ui +) set_target_properties(Cutter PROPERTIES OUTPUT_NAME cutter RUNTIME_OUTPUT_DIRECTORY .. diff --git a/src/core/BinDiff.cpp b/src/core/BinDiff.cpp index cc2e3e6ec7..8a2a6d36d9 100644 --- a/src/core/BinDiff.cpp +++ b/src/core/BinDiff.cpp @@ -57,10 +57,18 @@ void BinDiff::run() continueRun = true; maxTotal = 1; // maxTotal must be at least 1. mutex.unlock(); +<<<<<<< HEAD result = Core()->diffNewFile(file, level, compareLogic, threadCallback, this); Core()->diffData.setAnalysisMatchResult(result); +======= + cutterDiff->initCores(); + cutterDiff->openFiles(fileA,fileB); + cutterDiff->analyzeCores(level); + cutterDiff->syncConfig(); + result = cutterDiff->matchFunctions(compareLogic, threadCallback, this); +>>>>>>> 587a4b6e (Cutter Diff) mutex.lock(); const bool canComplete = continueRun; diff --git a/src/core/CutterDiff.cpp b/src/core/CutterDiff.cpp new file mode 100644 index 0000000000..f9696da5b6 --- /dev/null +++ b/src/core/CutterDiff.cpp @@ -0,0 +1,237 @@ +#include "CutterDiff.h" + +#include "Configuration.h" + +CutterDiff::CutterDiff(QObject *parent) : QObject { parent } {} + +CutterDiff::~CutterDiff(){ + rz_core_free(coreA); + rz_core_free(coreB); +} + +bool CutterDiff::initCores(){ + mutex.lock(); + + rz_core_free(coreA); + rz_core_free(coreB); + + coreA = rz_core_new(); + coreB = rz_core_new(); + + if(!(coreA||coreB)){ + goto fail; + } + + rz_core_loadlibs(coreA, RZ_CORE_LOADLIBS_ALL); + rz_core_loadlibs(coreB, RZ_CORE_LOADLIBS_ALL); + + coreA->print->scr_prompt = false; + coreB->print->scr_prompt = false; + mutex.unlock(); + return true; +fail: + rz_core_free(coreA); + rz_core_free(coreB); + mutex.unlock(); + return false; +} + +bool CutterDiff::openFiles(const QString &fileA, const QString &fileB){ + mutex.lock(); + // open core files + if (!rz_core_file_open(coreA, fileA.toUtf8().constData(), RZ_PERM_RX, 0)) { + qWarning() << tr("cannot open file %1").arg(fileA); + goto fail; + } + + if (!rz_core_file_open(coreB, fileB.toUtf8().constData(), RZ_PERM_RX, 0)) { + qWarning() << tr("cannot open file %1").arg(fileB); + goto fail; + } + + if (!rz_core_bin_load(coreA, nullptr, UT64_MAX)) { + qWarning() << tr("cannot load bin %1").arg(fileA); + goto fail; + } + + if (!rz_core_bin_load(coreB, nullptr, UT64_MAX)) { + qWarning() << tr("cannot load bin %1").arg(fileB); + goto fail; + } + + if (!rz_core_bin_update_arch_bits(coreA)) { + qWarning() << tr("cannot set architecture with bits in fileA"); + goto fail; + } + + if (!rz_core_bin_update_arch_bits(coreB)) { + qWarning() << tr("cannot set architecture with bits in fileB"); + goto fail; + } + syncConfig(); + mutex.unlock(); + return true; +fail: + rz_core_file_close_all_but(coreA); + rz_core_file_close_all_but(coreB); + mutex.unlock(); + return false; +} + +void CutterDiff::syncConfig(){ + RzConfigEntry *var; + RzConfigNode *node; + RzCoreLocked core(Core()); + CutterRzVectorForeach(&core->config->sorted_vars, var, RzConfigEntry) + { + node = &var->node; + if (!strcmp(node->name, "scr.color") || !strcmp(node->name, "scr.interactive") + || !strcmp(node->name, "cfg.debug")) { + rz_config_set(coreA->config, node->name, "0"); + rz_config_set(coreB->config, node->name, "0"); + continue; + } + rz_config_set(coreA->config, node->name, node->value); + rz_config_set(coreB->config, node->name, node->value); + } +} + +bool CutterDiff::analyzeCores(int level){ + mutex.lock(); + if (!rz_core_analysis_all(coreA)) { + qWarning() << tr("cannot perform basic analysis of the binary fileA"); + goto fail; + } + if (!rz_core_analysis_all(coreB)) { + qWarning() << tr("cannot perform basic analysis of the binary fileB"); + goto fail; + } + + if (level != AnalysisLevelSymbols + && !rz_core_analysis_everything(coreA, level == AnalysisLevelExperimental, nullptr)) { + qWarning() << tr("cannot perform complete analysis of the binaryA"); + goto fail; + } + + if (level != AnalysisLevelSymbols + && !rz_core_analysis_everything(coreB, level == AnalysisLevelExperimental, nullptr)) { + qWarning() << tr("cannot perform complete analysis of the binaryB"); + goto fail; + } +fail: + mutex.unlock(); + return false; +} + +QList CutterDiff::getFunctionList(bool orig) +{ + mutex.lock(); + QList list; + const RzList *functions = rz_analysis_function_list(orig ? coreA->analysis : coreB->analysis); + if (!functions) { + mutex.unlock(); + return list; + } + RzAnalysisFunction *func = nullptr; + const RzListIter *it = nullptr; + CutterRzListForeach (functions, it, RzAnalysisFunction, func) { + FunctionDescription desc; + desc.offset = func->addr; + desc.linearSize = rz_analysis_function_linear_size(const_cast(func)); + desc.nargs = rz_analysis_arg_count(const_cast(func)); + desc.nlocals = rz_analysis_var_local_count(const_cast(func)); + desc.nbbs = rz_pvector_len(func->bbs); + desc.calltype = func->cc ? QString::fromUtf8(func->cc) : QString(); + desc.name = func->name ? QString::fromUtf8(func->name) : QString(); + desc.edges = rz_analysis_function_count_edges(func, nullptr); + desc.stackframe = func->maxstack; + list.push_back(desc); + } + mutex.unlock(); + return list; +} + +#define IS_IMPORT(name) \ +(name.startsWith("sym.imp.") || name.startsWith("loc.imp.") || name.startsWith("imp.")) +#define IS_SYMBOL(name, pfx) (IS_IMPORT(name) || name.startsWith(pfx)) + +RzList *CutterDiff::getFunctions(RzAnalysis *analysis, int compareLogic) +{ + + const RzList *functions = rz_analysis_function_list(analysis); + if (!functions) { + return nullptr; + } + + RzList *list = rz_list_newf(nullptr); + if (!list) { + return list; + } + + QString pfx = Config()->getConfigString("analysis.fcnprefix"); + if (pfx.isEmpty()) { + pfx = "fcn."; + } else { + pfx += "."; + } + + RzAnalysisFunction *func = nullptr; + const RzListIter *it = nullptr; + + CutterRzListForeach (functions, it, RzAnalysisFunction, func) { + const QString name = func->name; + if (compareLogic == CutterDiff::CompareLogicDefault && IS_IMPORT(name)) { + continue; + } else if (compareLogic == CutterDiff::CompareLogicSymbols && IS_SYMBOL(name, pfx)) { + continue; + } + rz_list_add_sorted(list, func, rz_analysis_get_column_sort(analysis), nullptr); + } + + return list; +} + + + +RzAnalysisMatchResult *CutterDiff::matchFunctions(int compareLogic,RzAnalysisMatchThreadInfoCb callback, void *user) +{ + mutex.lock(); + RzList *fcnsA = nullptr, *fcnsB = nullptr; + RzAnalysisMatchResult *result = nullptr; + RzAnalysisMatchOpt opts; + + fcnsA = getFunctions(coreA->analysis, compareLogic); + if (rz_list_empty(fcnsA)) { + qWarning() << tr("no functions found in the current opened file"); + goto fail; + } + + fcnsB = getFunctions(coreB->analysis, compareLogic); + if (rz_list_empty(fcnsB)) { + qWarning() << tr("no functions found in the just opene file %1"); + goto fail; + } + + opts.analysis_a = coreA->analysis; + opts.analysis_b = coreB->analysis; + opts.callback = callback; + opts.user = user; + + // calculate all the matches between the functions of the 2 different core files. + result = rz_analysis_match_functions(fcnsA, fcnsB, &opts); + if (!result) { + qWarning() << tr("failed to perform the function matching operation or job was cancelled."); + goto fail; + } + + rz_list_free(fcnsA); + rz_list_free(fcnsB); + return result; + +fail: + + rz_list_free(fcnsA); + rz_list_free(fcnsB); + // rz_core_file_close_all_but(diffCore); + return nullptr; +} diff --git a/src/core/CutterDiff.h b/src/core/CutterDiff.h new file mode 100644 index 0000000000..058d051222 --- /dev/null +++ b/src/core/CutterDiff.h @@ -0,0 +1,49 @@ +#ifndef CUTTERDIFF_H +#define CUTTERDIFF_H + +#include +#include + + +#include "RizinCpp.h" +#include "CutterCommon.h" +#include "Cutter.h" + + +class RzDiffCoreLocked; + +class CUTTER_EXPORT CutterDiff : public QObject +{ + Q_OBJECT +public: + explicit CutterDiff(QObject *parent = nullptr); + ~CutterDiff(); + bool initCores(); + bool openFiles(const QString &fileA, const QString &fileB); + bool analyzeCores(int level); + void syncConfig(); + RzAnalysisMatchResult *matchFunctions(int compareLogic, RzAnalysisMatchThreadInfoCb callback, + void *user); + enum : ut8 { + AnalysisLevelSymbols, + AnalysisLevelAuto, + AnalysisLevelExperimental + }; + + enum : ut8 { + CompareLogicDefault = 0, ///< Only symbols and functions (no imports) + CompareLogicComplete, ///< All functions (imports included) + CompareLogicSymbols, ///< Only symbols + }; + QList getFunctionList(bool orig = true); + +private: + RzCore *coreA = nullptr; + RzCore *coreB = nullptr; + void storeFunctions(); + QRecursiveMutex mutex; + RzList *getFunctions(RzAnalysis *analysis, int compareLogic); +signals: +}; + +#endif // CUTTERDIFF_H diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp index 5488472ce6..5e5280aa44 100644 --- a/src/core/MainWindow.cpp +++ b/src/core/MainWindow.cpp @@ -115,7 +115,11 @@ // Tools #include "tools/basefind/BaseFindDialog.h" +<<<<<<< HEAD #include "tools/bindiff/DiffLoadDialog.h" +======= +#include "tools/bindiff/CutterDiffWindow.h" +>>>>>>> 587a4b6e (Cutter Diff) template T *getNewInstance(MainWindow *m) @@ -1752,7 +1756,11 @@ void MainWindow::onActionRefreshPanelsTriggered() void MainWindow::onActionDiffFilesTriggered() { +<<<<<<< HEAD auto diffFilesDialog = new DiffLoadDialog(this); +======= + auto diffFilesDialog = new CutterDiffWindow(); +>>>>>>> 587a4b6e (Cutter Diff) diffFilesDialog->show(); } From ea78bb6e830c49db9ab588167862951f5c695af3 Mon Sep 17 00:00:00 2001 From: NewtronReal Date: Sat, 20 Jun 2026 22:02:31 +0530 Subject: [PATCH 06/13] PR draft commit turn off CUTTER_ASAN --- CMakeLists.txt | 3 + cmake/BundledRizin.cmake | 7 +- src/CMakeLists.txt | 6 + src/core/BinDiff.cpp | 17 ++- src/core/CutterDiff.cpp | 121 +++++++++++++++---- src/core/CutterDiff.h | 27 +++-- src/tools/bindiff/CutterDiffWindow.cpp | 97 +++++++++++---- src/tools/bindiff/CutterDiffWindow.h | 13 ++ src/tools/bindiff/CutterDiffWindow.ui | 160 ++++++++++++++++++++++--- src/tools/bindiff/DiffWaitDialog.cpp | 1 - src/tools/bindiff/HexDiff.cpp | 57 ++++++--- src/tools/bindiff/HexDiff.h | 90 ++++++++++++-- 12 files changed, 501 insertions(+), 98 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e785237fc0..954bf8df7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ set("CUTTER_QT" 6 CACHE STRING "Major QT version to use 5|6") set_property(CACHE "CUTTER_QT" PROPERTY STRINGS 5 6) option(CUTTER_INCLUDE_GIT_HASH "Include git hash in full version" ON) set(CUTTER_VERSION_SUFFIX "" CACHE STRING "Can be used by packagers to differentiate multiple packages using same source, for example build number or presence of additional patches.") +option(CUTTER_ASAN "Build with ASAN" OFF) if(NOT CUTTER_ENABLE_PYTHON) set(CUTTER_ENABLE_PYTHON_BINDINGS OFF) @@ -186,11 +187,13 @@ message(STATUS "- Package RzSilhouette: ${CUTTER_PACKAGE_RZ_SILHOUETTE}") message(STATUS "- Package JSDec: ${CUTTER_PACKAGE_JSDEC}") message(STATUS "- QT: ${CUTTER_QT}") message(STATUS "") +message(STATUS "- ASAN: ${CUTTER_ASAN}") if (CUTTER_QT LESS 5 OR CUTTER_QT GREATER 6) message(FATAL_ERROR "Unsupported QT major version") endif() + add_subdirectory(src) if(CUTTER_ENABLE_PACKAGING) diff --git a/cmake/BundledRizin.cmake b/cmake/BundledRizin.cmake index b89399b417..e51d10f6ef 100644 --- a/cmake/BundledRizin.cmake +++ b/cmake/BundledRizin.cmake @@ -22,6 +22,11 @@ if (CUTTER_ENABLE_SIGDB) list(APPEND MESON_OPTIONS "-Dinstall_sigdb=true") endif() +# Enable ASan/UBSan in bundled Rizin when Cutter ASAN is enabled +if(CUTTER_ASAN) + list(APPEND MESON_OPTIONS "-Db_sanitize=address,undefined") +endif() + find_program(MESON meson) if(NOT MESON) message(FATAL_ERROR "Failed to find meson, which is required to build bundled rizin") @@ -85,4 +90,4 @@ if (WIN32) else () install(DIRECTORY "${RIZIN_INSTALL_DIR}/" DESTINATION "." USE_SOURCE_PERMISSIONS PATTERN "rz-test" EXCLUDE) -endif() +endif() \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8421c58645..8b0828c2d3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -609,6 +609,12 @@ if(CUTTER_ENABLE_PYTHON) endif() endif() +if(CUTTER_ASAN) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdiagnostics-color=always -fsanitize=address,undefined -fno-omit-frame-pointer -fsanitize-address-use-after-scope") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always -fsanitize=address,undefined -fno-omit-frame-pointer -fsanitize-address-use-after-scope") + set(CMAKE_LINKER_FLAGS "${CMAKE_LINKER_FLAGS} -fdiagnostics-color=always -fsanitize=address,undefined -fno-omit-frame-pointer -fsanitize-address-use-after-scope") +endif() + if(TARGET KF${CUTTER_QT}::SyntaxHighlighting) target_link_libraries(Cutter PRIVATE KF${CUTTER_QT}::SyntaxHighlighting) target_compile_definitions(Cutter PRIVATE CUTTER_ENABLE_KSYNTAXHIGHLIGHTING) diff --git a/src/core/BinDiff.cpp b/src/core/BinDiff.cpp index 8a2a6d36d9..d1b7f1ca75 100644 --- a/src/core/BinDiff.cpp +++ b/src/core/BinDiff.cpp @@ -64,7 +64,7 @@ void BinDiff::run() Core()->diffData.setAnalysisMatchResult(result); ======= cutterDiff->initCores(); - cutterDiff->openFiles(fileA,fileB); + cutterDiff->openFiles(fileA, fileB); cutterDiff->analyzeCores(level); cutterDiff->syncConfig(); result = cutterDiff->matchFunctions(compareLogic, threadCallback, this); @@ -85,6 +85,7 @@ void BinDiff::cancel() mutex.unlock(); } +<<<<<<< HEAD // static void setFunctionDescription(FunctionDescription *desc, const RzAnalysisFunction *func) // { // desc->offset = func->addr; @@ -97,6 +98,20 @@ void BinDiff::cancel() // desc->edges = rz_analysis_function_count_edges(func, nullptr); // desc->stackframe = func->maxstack; // } +======= +static void setFunctionDescription(FunctionDescription *desc, const RzAnalysisFunction *func) +{ + desc->offset = func->addr; + desc->linearSize = rz_analysis_function_linear_size(const_cast(func)); + desc->nargs = rz_analysis_arg_count(const_cast(func)); + desc->nlocals = rz_analysis_var_local_count(const_cast(func)); + desc->nbbs = rz_pvector_len(func->bbs); + desc->calltype = func->cc ? QString::fromUtf8(func->cc) : QString(); + desc->name = func->name ? QString::fromUtf8(func->name) : QString(); + desc->edges = rz_analysis_function_count_edges(func, nullptr); + desc->stackframe = func->maxstack; +} +>>>>>>> a8934965 (PR draft commit) // QList BinDiff::matches() // { diff --git a/src/core/CutterDiff.cpp b/src/core/CutterDiff.cpp index f9696da5b6..41ed748b47 100644 --- a/src/core/CutterDiff.cpp +++ b/src/core/CutterDiff.cpp @@ -2,15 +2,28 @@ #include "Configuration.h" -CutterDiff::CutterDiff(QObject *parent) : QObject { parent } {} +#include -CutterDiff::~CutterDiff(){ +#define LOCK() const QMutexLocker locker(&mutex) + +CutterDiff::CutterDiff(QObject *parent) + : QObject { parent } +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + , + mutex(QMutex::Recursive) +#endif +{ +} + +CutterDiff::~CutterDiff() +{ rz_core_free(coreA); rz_core_free(coreB); } -bool CutterDiff::initCores(){ - mutex.lock(); +bool CutterDiff::initCores() +{ + LOCK(); rz_core_free(coreA); rz_core_free(coreB); @@ -18,7 +31,7 @@ bool CutterDiff::initCores(){ coreA = rz_core_new(); coreB = rz_core_new(); - if(!(coreA||coreB)){ + if (!(coreA || coreB)) { goto fail; } @@ -27,17 +40,17 @@ bool CutterDiff::initCores(){ coreA->print->scr_prompt = false; coreB->print->scr_prompt = false; - mutex.unlock(); return true; fail: rz_core_free(coreA); rz_core_free(coreB); - mutex.unlock(); return false; } -bool CutterDiff::openFiles(const QString &fileA, const QString &fileB){ - mutex.lock(); +bool CutterDiff::openFiles(const QString &fileA, const QString &fileB) +{ + + LOCK(); // open core files if (!rz_core_file_open(coreA, fileA.toUtf8().constData(), RZ_PERM_RX, 0)) { qWarning() << tr("cannot open file %1").arg(fileA); @@ -69,16 +82,15 @@ bool CutterDiff::openFiles(const QString &fileA, const QString &fileB){ goto fail; } syncConfig(); - mutex.unlock(); return true; fail: rz_core_file_close_all_but(coreA); rz_core_file_close_all_but(coreB); - mutex.unlock(); return false; } -void CutterDiff::syncConfig(){ +void CutterDiff::syncConfig() +{ RzConfigEntry *var; RzConfigNode *node; RzCoreLocked core(Core()); @@ -96,8 +108,9 @@ void CutterDiff::syncConfig(){ } } -bool CutterDiff::analyzeCores(int level){ - mutex.lock(); +bool CutterDiff::analyzeCores(int level) +{ + LOCK(); if (!rz_core_analysis_all(coreA)) { qWarning() << tr("cannot perform basic analysis of the binary fileA"); goto fail; @@ -118,18 +131,17 @@ bool CutterDiff::analyzeCores(int level){ qWarning() << tr("cannot perform complete analysis of the binaryB"); goto fail; } + return true; fail: - mutex.unlock(); return false; } QList CutterDiff::getFunctionList(bool orig) { - mutex.lock(); + LOCK(); QList list; const RzList *functions = rz_analysis_function_list(orig ? coreA->analysis : coreB->analysis); if (!functions) { - mutex.unlock(); return list; } RzAnalysisFunction *func = nullptr; @@ -147,17 +159,16 @@ QList CutterDiff::getFunctionList(bool orig) desc.stackframe = func->maxstack; list.push_back(desc); } - mutex.unlock(); return list; } #define IS_IMPORT(name) \ -(name.startsWith("sym.imp.") || name.startsWith("loc.imp.") || name.startsWith("imp.")) + (name.startsWith("sym.imp.") || name.startsWith("loc.imp.") || name.startsWith("imp.")) #define IS_SYMBOL(name, pfx) (IS_IMPORT(name) || name.startsWith(pfx)) RzList *CutterDiff::getFunctions(RzAnalysis *analysis, int compareLogic) { - + LOCK(); const RzList *functions = rz_analysis_function_list(analysis); if (!functions) { return nullptr; @@ -191,11 +202,30 @@ RzList *CutterDiff::getFunctions(RzAnalysis *analysis, int compareLogic) return list; } +QByteArray CutterDiff::ioRead(RVA addr, int len, bool orig) +{ + LOCK(); + const RzCore *core = orig ? coreA : coreB; + QByteArray array; + + if (len <= 0) { + return array; + } + + /* Zero-copy */ + array.resize(len); + if (!core || !core->io + || !rz_io_read_at_mapped(core->io, addr, reinterpret_cast(array.data()), len)) { + array.fill(0xff); + } + return array; +} -RzAnalysisMatchResult *CutterDiff::matchFunctions(int compareLogic,RzAnalysisMatchThreadInfoCb callback, void *user) +RzAnalysisMatchResult *CutterDiff::matchFunctions(int compareLogic, + RzAnalysisMatchThreadInfoCb callback, void *user) { - mutex.lock(); + LOCK(); RzList *fcnsA = nullptr, *fcnsB = nullptr; RzAnalysisMatchResult *result = nullptr; RzAnalysisMatchOpt opts; @@ -217,7 +247,7 @@ RzAnalysisMatchResult *CutterDiff::matchFunctions(int compareLogic,RzAnalysisMat opts.callback = callback; opts.user = user; - // calculate all the matches between the functions of the 2 different core files. + // calculate all the matches between the functions of the 2 different core files. result = rz_analysis_match_functions(fcnsA, fcnsB, &opts); if (!result) { qWarning() << tr("failed to perform the function matching operation or job was cancelled."); @@ -235,3 +265,48 @@ RzAnalysisMatchResult *CutterDiff::matchFunctions(int compareLogic,RzAnalysisMat // rz_core_file_close_all_but(diffCore); return nullptr; } + +QString CutterDiff::getCommentAt(RVA addr, bool orig) +{ + LOCK(); + const RzCore *core = orig ? coreA : coreB; + if (!core->analysis) { + return ""; + } + return rz_meta_get_string(core->analysis, RZ_META_TYPE_COMMENT, addr); +} + +QString CutterDiff::listFlagsAsStringAt(RVA addr, bool orig) +{ + LOCK(); + const RzCore *core = orig ? coreA : coreB; + if (!core || !core->flags) { + return ""; + } + char *flagList = rz_flag_get_liststr(core->flags, addr); + QString result = fromOwnedCharPtr(flagList); + return result; +} + +bool CutterDiff::cmpBytesAt(RVA addrA, RVA addrB, size_t len) +{ + LOCK(); + if (len <= 0) { + return true; + } + if (!coreA || !coreB || !coreA->io || !coreB->io) { + qWarning() << "Cores not initialized"; + return false; + } + QByteArray bufA; + bufA.resize(len); + QByteArray bufB; + bufB.resize(len); + + if (!rz_io_read_at_mapped(coreA->io, addrA, reinterpret_cast(bufA.data()), len) + || !rz_io_read_at_mapped(coreB->io, addrB, reinterpret_cast(bufB.data()), len)) { + qWarning() << "Read failed"; + return false; + }; + return bufA.compare(bufB) == 0; +} diff --git a/src/core/CutterDiff.h b/src/core/CutterDiff.h index 058d051222..b1e73fbff2 100644 --- a/src/core/CutterDiff.h +++ b/src/core/CutterDiff.h @@ -1,14 +1,12 @@ #ifndef CUTTERDIFF_H #define CUTTERDIFF_H -#include -#include - - -#include "RizinCpp.h" -#include "CutterCommon.h" #include "Cutter.h" +#include "CutterCommon.h" +#include "RizinCpp.h" +#include +#include class RzDiffCoreLocked; @@ -24,11 +22,7 @@ class CUTTER_EXPORT CutterDiff : public QObject void syncConfig(); RzAnalysisMatchResult *matchFunctions(int compareLogic, RzAnalysisMatchThreadInfoCb callback, void *user); - enum : ut8 { - AnalysisLevelSymbols, - AnalysisLevelAuto, - AnalysisLevelExperimental - }; + enum : ut8 { AnalysisLevelSymbols, AnalysisLevelAuto, AnalysisLevelExperimental }; enum : ut8 { CompareLogicDefault = 0, ///< Only symbols and functions (no imports) @@ -36,13 +30,20 @@ class CUTTER_EXPORT CutterDiff : public QObject CompareLogicSymbols, ///< Only symbols }; QList getFunctionList(bool orig = true); + QByteArray ioRead(RVA addr, int len, bool orig); + RzList *getFunctions(RzAnalysis *analysis, int compareLogic); + QString getCommentAt(RVA addr, bool orig); + QString listFlagsAsStringAt(RVA addr, bool orig); + bool cmpBytesAt(RVA addrA, RVA addrB, size_t len); private: RzCore *coreA = nullptr; RzCore *coreB = nullptr; - void storeFunctions(); +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + QMutex mutex; +#else QRecursiveMutex mutex; - RzList *getFunctions(RzAnalysis *analysis, int compareLogic); +#endif signals: }; diff --git a/src/tools/bindiff/CutterDiffWindow.cpp b/src/tools/bindiff/CutterDiffWindow.cpp index e8e699812d..4f984b573e 100644 --- a/src/tools/bindiff/CutterDiffWindow.cpp +++ b/src/tools/bindiff/CutterDiffWindow.cpp @@ -9,18 +9,33 @@ FunctionListModel::FunctionListModel(QList *list, QObject * : list(list), AddressableItemModel<>(parent) { } -QModelIndex FunctionListModel::index(int row, int column, const QModelIndex &) const +QModelIndex FunctionListModel::index(int row, int column, const QModelIndex &parent) const { - return createIndex(row, column, (quintptr)0); + if (parent.isValid()) { + return QModelIndex(); + } + + if (row < 0 || row >= list->size()) { + return QModelIndex(); + } + + if (column < 0 || column >= ColumnCount) { + return QModelIndex(); + } + + return createIndex(row, column); } QModelIndex FunctionListModel::parent(const QModelIndex &) const { - return this->index(0, 0); + return QModelIndex(); } -int FunctionListModel::rowCount(const QModelIndex &) const +int FunctionListModel::rowCount(const QModelIndex &parent) const { + if (parent.isValid()) { + return 0; + } return list->size(); } @@ -31,6 +46,14 @@ int FunctionListModel::columnCount(const QModelIndex &) const QVariant FunctionListModel::data(const QModelIndex &index, int role) const { + if (!index.isValid()) { + return QVariant(); + } + + if (index.row() < 0 || index.row() >= list->size()) { + return QVariant(); + } + switch (role) { case Qt::DisplayRole: switch (index.column()) { @@ -92,9 +115,13 @@ DiffMatchModel::DiffMatchModel(QList *list, QColor cPer { } -int DiffMatchModel::rowCount(const QModelIndex &) const +int DiffMatchModel::rowCount(const QModelIndex &parent) const { - return list->count(); + if (parent.isValid()) { + return 0; + } + + return list->size(); } int DiffMatchModel::columnCount(const QModelIndex &) const @@ -293,12 +320,15 @@ CutterDiffWindow::CutterDiffWindow(QWidget *parent) ui(new Ui::CutterDiffWindow) { ui->setupUi(this); + ui->splitter->setSizes({ 250, 750 }); + ui->splitter->setStretchFactor(0, 1); + ui->splitter->setStretchFactor(1, 3); cutterDiff->initCores(); connect(bDiff, &BinDiff::complete, this, &CutterDiffWindow::onBinDiffCompleted); connect(ui->actionDiffNewFiles, &QAction::triggered, this, &CutterDiffWindow::onActionDiffNewFile); + ui->tabWidget2->hide(); setupFonts(); - refreshHex(RVA_INVALID); } CutterDiffWindow::~CutterDiffWindow() @@ -306,19 +336,15 @@ CutterDiffWindow::~CutterDiffWindow() delete ui; } -void CutterDiffWindow::setupFonts() -{ - const QFont font = Config()->getFont(); - ui->hexDiffTextView->setMonospaceFont(font); -} +void CutterDiffWindow::setupFonts() {} void CutterDiffWindow::refreshHex(RVA addr) { - if (addr != RVA_INVALID) { - ui->hexDiffTextView->seek(addr); - } else { - ui->hexDiffTextView->refresh(); - } + // if (addr != RVA_INVALID) { + // ui->hexDiffTextView->seek(addr); + // } else { + // ui->hexDiffTextView->refresh(); + // } } void CutterDiffWindow::onBinDiffCompleted() @@ -350,13 +376,26 @@ void CutterDiffWindow::onBinDiffCompleted() ui->treeViewAdded->sortByColumn(DiffMismatchModel::FuncName, Qt::AscendingOrder); ui->treeViewAdded->setContextMenuPolicy(Qt::CustomContextMenu); - // fcnsA = cutterDiff->getFunctionList(true); - // fcnsB = cutterDiff->getFunctionList(false); - // modelA = new FunctionListModel(&fcnsA,this); - // modelB = new FunctionListModel(&fcnsB,this); - - // ui->treeViewFcnsA->setModel(modelA); - // ui->treeViewFcnsB->setModel(modelB); + fcnsA = cutterDiff->getFunctionList(true); + fcnsB = cutterDiff->getFunctionList(false); + modelA = new FunctionListModel(&fcnsA, this); + modelB = new FunctionListModel(&fcnsB, this); + + ui->treeViewFcnsA->setModel(modelA); + ui->treeViewFcnsB->setModel(modelB); + addHexDiff(); + connect(ui->treeViewFcnsA, &CutterTreeView::clicked, this, + [this](const QModelIndex &index) { hexDiff->seek(modelA->address(index), true); }); + connect(ui->treeViewFcnsB, &CutterTreeView::clicked, this, + [this](const QModelIndex &index) { hexDiff->seek(modelB->address(index), false); }); + + // Transpose + + connect(ui->shiftUpA, &QPushButton::clicked, this, [this]() { hexDiff->transpose(-1, 0); }); + connect(ui->shiftDownA, &QPushButton::clicked, this, [this]() { hexDiff->transpose(1, 0); }); + connect(ui->shiftUpB, &QPushButton::clicked, this, [this]() { hexDiff->transpose(0, -1); }); + connect(ui->shiftDownB, &QPushButton::clicked, this, [this]() { hexDiff->transpose(0, 1); }); + ui->tabWidget2->show(); } void CutterDiffWindow::onActionDiffNewFile() @@ -364,3 +403,13 @@ void CutterDiffWindow::onActionDiffNewFile() auto loadDiff = new DiffLoadDialog(bDiff, this); loadDiff->show(); } + +void CutterDiffWindow::addHexDiff() +{ + if (!hexDiff) { + hexDiff = new HexDiff(cutterDiff, this); + } + ui->hexDiffContainer->layout()->addWidget(hexDiff); + const QFont font = Config()->getFont(); + hexDiff->setMonospaceFont(font); +} diff --git a/src/tools/bindiff/CutterDiffWindow.h b/src/tools/bindiff/CutterDiffWindow.h index 3bfaa6759a..05423828a9 100644 --- a/src/tools/bindiff/CutterDiffWindow.h +++ b/src/tools/bindiff/CutterDiffWindow.h @@ -1,6 +1,8 @@ #ifndef CUTTERDIFFWINDOW_H #define CUTTERDIFFWINDOW_H +#include "HexDiff.h" + #include #include @@ -121,17 +123,28 @@ public slots: private: Ui::CutterDiffWindow *ui; CutterDiff *cutterDiff; + // BinDiff thread which fetches basic analysis results and processing BinDiff *bDiff; + // model for matched functions of both binaries DiffMatchModel *matches; + // model for added functions DiffMismatchModel *added; + // model for removed functions DiffMismatchModel *removed; + // match results QList listMatch; + // added function descriptions QList listDel; + // removed function descriptions QList listAdd; + // model a functions FunctionListModel *modelA; + // model b functions FunctionListModel *modelB; QList fcnsA; QList fcnsB; + HexDiff *hexDiff = nullptr; + void addHexDiff(); void setupFonts(); void refreshHex(RVA addr); }; diff --git a/src/tools/bindiff/CutterDiffWindow.ui b/src/tools/bindiff/CutterDiffWindow.ui index 3228d19e84..6083d81e4a 100644 --- a/src/tools/bindiff/CutterDiffWindow.ui +++ b/src/tools/bindiff/CutterDiffWindow.ui @@ -6,8 +6,8 @@ 0 0 - 800 - 600 + 581 + 444 @@ -16,7 +16,13 @@ - + + + + 0 + 0 + + 300 @@ -36,6 +42,12 @@ false + + + 0 + 0 + + 100 @@ -45,7 +57,7 @@ Qt::Orientation::Vertical - + @@ -63,7 +75,7 @@ - + @@ -83,6 +95,12 @@ + + + 0 + 0 + + 3 @@ -116,13 +134,133 @@ - + Page + + 0 + + + 0 + + + 0 + + + 0 + - + + + Qt::Orientation::Horizontal + + + + + + + + 0 + + + + + + 0 + 0 + + + + 0 + + + + Diffing + + + + + + Shift + + + + + + + + + + + File A + + + + + + + + + + + + + + + + + + + + + + + + + FileB + + + + + + + + + + + + + + + + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 174 + + + + + + + + + + + @@ -136,7 +274,7 @@ 0 0 - 800 + 581 22 @@ -162,12 +300,6 @@
widgets/CutterTreeView.h
1 - - HexDiff - QWidget -
tools/bindiff/HexDiff.h
- 1 -
diff --git a/src/tools/bindiff/DiffWaitDialog.cpp b/src/tools/bindiff/DiffWaitDialog.cpp index 4134f820a0..9844e95d2a 100644 --- a/src/tools/bindiff/DiffWaitDialog.cpp +++ b/src/tools/bindiff/DiffWaitDialog.cpp @@ -55,7 +55,6 @@ void DiffWaitDialog::show(QString original, QString modified, int level, int com timer.start(1000); bDiff->start(); - qInfo() << "started"; this->QDialog::show(); } diff --git a/src/tools/bindiff/HexDiff.cpp b/src/tools/bindiff/HexDiff.cpp index 96b0afec42..882c634025 100644 --- a/src/tools/bindiff/HexDiff.cpp +++ b/src/tools/bindiff/HexDiff.cpp @@ -35,7 +35,7 @@ constexpr int maxLineWidthBytes = 128 * 1024; constexpr int warningTimeMs = 500; } -HexDiff::HexDiff(QWidget *parent) +HexDiff::HexDiff(CutterDiff *cutterDiff, QWidget *parent) : QScrollArea(parent), cursorEnabled(true), cursorArea(DiffArea::ItemA), @@ -52,7 +52,8 @@ HexDiff::HexDiff(QWidget *parent) showExHex(true), showExAddr(true), warningTimer(this), - vScrollBar(new AddressRangeScrollBar(this)) + vScrollBar(new AddressRangeScrollBar(this)), + cutterDiff(cutterDiff) { setMouseTracking(true); setFocusPolicy(Qt::FocusPolicy::StrongFocus); @@ -155,8 +156,8 @@ HexDiff::HexDiff(QWidget *parent) startAddress = 0ULL; cursor.address = 0ULL; - ctxA.data.reset(new MemoryData()); - ctxB.data.reset(new MemoryData()); + ctxA.data.reset(new MemoryDiffData(cutterDiff, true)); + ctxB.data.reset(new MemoryDiffData(cutterDiff, false)); ctxA.file = DiffFile::A; ctxB.file = DiffFile::B; @@ -343,8 +344,15 @@ HexDiff::Selection HexDiff::getSelection() return Selection { selection.isEmpty(), selection.start(), selection.end() }; } -void HexDiff::seek(uint64_t address) +void HexDiff::seek(uint64_t address, bool orig) { + if (!orig) { + if (relTranspose >= 0) { + address -= static_cast(relTranspose); + } else { + address += static_cast(-relTranspose); + } + } if (cursorArea > 1) { // when other widget causes seek to the middle of word // switch to ascii column which operates with byte positions @@ -972,7 +980,7 @@ void HexDiff::drawAddrArea(QPainter &painter, DiffFileContext &ctx) uint64_t HexDiff::ctxAddr(uint64_t addrA, DiffFileContext &ctx) { - if (ctxA.file == DiffFile::A) { + if (ctx.file == DiffFile::A) { return getAddressB(addrA); } return addrA; @@ -1024,16 +1032,30 @@ void HexDiff::drawItemArea(QPainter &painter, DiffFileContext &ctx) itemColor = palette().highlightedText().color(); } + if (ctx.file == DiffFile::A && diffItemsAt(itemAddr)) { + itemColor = warningColor; + } + + if (ctx.file == DiffFile::B && diffItemsAt(getAddressA(itemAddr))) { + itemColor = warningColor; + } + painter.setPen(itemColor); painter.drawText(itemRect, Qt::AlignVCenter, itemString); itemRect.translate(itemWidth(), 0); - if (ctx.file == DiffFile::A && itemAddr == cursor.address) { - itemCursor.cachedChar = itemString.at(0); - itemCursor.cachedColor = itemColor; + + if (ctx.file == DiffFile::A) { + if (itemAddr == cursor.address) { + itemCursor.cachedChar = itemString.at(0); + itemCursor.cachedColor = itemColor; + } } - if (ctx.file == DiffFile::B && itemAddr == getAddressB(cursor.address)) { - itemCursor.cachedCharB = itemString.at(0); - itemCursor.cachedColorB = itemColor; + + if (ctx.file == DiffFile::B) { + if (itemAddr == getAddressB(cursor.address)) { + itemCursor.cachedCharB = itemString.at(0); + itemCursor.cachedColorB = itemColor; + } } } itemRect.translate(columnSpacingWidth(), 0); @@ -1066,6 +1088,13 @@ void HexDiff::drawAsciiArea(QPainter &painter, DiffFileContext &ctx) if (selection.contains(address) && cursorArea < 2) { color = palette().highlightedText().color(); } + if (ctx.file == DiffFile::A && diffItemsAt(address)) { + color = warningColor; + } + + if (ctx.file == DiffFile::B && diffItemsAt(getAddressA(address))) { + color = warningColor; + } painter.setPen(color); /* Dots look ugly. Use fillRect() instead of drawText(). */ if (ascii == '.') { @@ -1596,10 +1625,10 @@ QChar HexDiff::renderAscii(int offset, DiffFileContext &ctx, QColor *color) */ QString HexDiff::getFlagsAndComment(uint64_t address, DiffFileContext &ctx) { // Needs to be redefined using BinDiffClass so that it can be used for both cores - const QString flagNames = Core()->listFlagsAsStringAt(address); + const QString flagNames = cutterDiff->listFlagsAsStringAt(address, ctx.file == DiffFile::A); QString metaData = flagNames.isEmpty() ? "" : "Flags: " + flagNames.trimmed(); - const QString comment = Core()->getCommentAt(address); + const QString comment = cutterDiff->getCommentAt(address, ctx.file == DiffFile::A); if (!comment.isEmpty()) { if (!metaData.isEmpty()) { metaData.append("\n"); diff --git a/src/tools/bindiff/HexDiff.h b/src/tools/bindiff/HexDiff.h index 097357cd41..c6fa4f1756 100644 --- a/src/tools/bindiff/HexDiff.h +++ b/src/tools/bindiff/HexDiff.h @@ -11,8 +11,75 @@ #include #include +#include #include +class MemoryDiffData +{ +public: + MemoryDiffData(CutterDiff *cutterDiff, bool orig) : cutterDiff(cutterDiff), orig(orig) {} + ~MemoryDiffData() {} + static constexpr size_t blockSize = 4096; + + void fetch(uint64_t address, int length) + { + // FIXME: reuse data if possible + // Will be fixing this since for diffing the core files needs efficient cache management + const uint64_t blockSize = 0x1000ULL; + const uint64_t alignedAddr = address & ~(blockSize - 1); + const int offset = address - alignedAddr; + int len = (offset + length + (blockSize - 1)) & ~(blockSize - 1); + mFirstBlockAddr = alignedAddr; + mLastValidAddr = length ? alignedAddr + len - 1 : 0; + if (mLastValidAddr < mFirstBlockAddr) { + mLastValidAddr = -1; + len = mLastValidAddr - mFirstBlockAddr + 1; + } + mBlocks.clear(); + uint64_t addr = alignedAddr; + for (ut64 i = 0; i < len / blockSize; ++i, addr += blockSize) { + mBlocks.append(cutterDiff->ioRead(addr, blockSize, orig)); + } + } + + bool copy(void *out, uint64_t addr, size_t len) + { + if (addr < mFirstBlockAddr + || addr > mLastValidAddr + /* do not merge with previous check to handle overflows */ + || (mLastValidAddr - addr + 1) < len || mBlocks.isEmpty()) { + memset(out, 0xff, len); + return false; + } + + const int totalOffset = addr - mFirstBlockAddr; + const int blockId = totalOffset / blockSize; + const int blockOffset = totalOffset % blockSize; + const size_t firstPart = blockSize - blockOffset; + if (firstPart >= len) { + memcpy(out, mBlocks.at(blockId).constData() + blockOffset, len); + } else { + memcpy(out, mBlocks.at(blockId).constData() + blockOffset, firstPart); + memcpy(static_cast(out) + firstPart, mBlocks.at(blockId + 1).constData(), + len - firstPart); + } + return true; + } + + bool write(const uint8_t, uint64_t, size_t) {} + + uint64_t maxIndex() { return std::numeric_limits::max(); } + + uint64_t minIndex() { return mFirstBlockAddr; } + +private: + bool orig = true; + CutterDiff *cutterDiff; + QVector mBlocks; + uint64_t mFirstBlockAddr = 0; + uint64_t mLastValidAddr = 0; +}; + // Defining DiffFile Struct for encapsulation enum DiffFile : ut8 { A, B }; @@ -29,7 +96,7 @@ class DiffFileContext public: DiffFile file; uint64_t startAddress; - std::unique_ptr data; + std::unique_ptr data; QRectF addrArea; QRectF itemArea; QRectF asciiArea; @@ -41,7 +108,6 @@ class DiffFileContext struct BasicDiffCursor { uint64_t address; - uint64_t addressB; bool pastEnd; explicit BasicDiffCursor(uint64_t pos) : address(pos), pastEnd(false) {} BasicDiffCursor() : address(0), pastEnd(false) {} @@ -190,7 +256,7 @@ class HexDiff : public QScrollArea Q_OBJECT public: - explicit HexDiff(QWidget *parent = nullptr); + explicit HexDiff(CutterDiff *cutterDiff, QWidget *parent = nullptr); ~HexDiff() override = default; void setMonospaceFont(const QFont &font); @@ -246,7 +312,7 @@ class HexDiff : public QScrollArea }; Selection getSelection(); public slots: - void seek(uint64_t address); + void seek(uint64_t address, bool orig = true); void refresh(); void updateColors(); signals: @@ -395,13 +461,21 @@ private slots: */ void setStartAddress(RVA address); - uint64_t getAddressB(uint64_t addr) const + uint64_t getAddressB(uint64_t addrA) const { if (relTranspose < 0) { - return addr - qAbs(relTranspose); + return addrA - qAbs(relTranspose); } - return addr + relTranspose; + return addrA + relTranspose; } + uint64_t getAddressA(uint64_t addrB) const + { + if (relTranspose < 0) { + return addrB + qAbs(relTranspose); + } + return addrB - relTranspose; + } + uint64_t getStartAddressB() const { return getAddressB(startAddress); } /** @@ -488,6 +562,8 @@ private slots: QRectF warningRect; QTimer warningTimer; + CutterDiff *cutterDiff; + AddressRangeScrollBar *vScrollBar; }; From b040a8781221d92e9bed0a7b4f051f5beac2fa04 Mon Sep 17 00:00:00 2001 From: NewtronReal Date: Tue, 23 Jun 2026 17:09:29 +0530 Subject: [PATCH 07/13] Work on parsing and info new addition unnecesssary unnecessary --- src/core/BinDiff.cpp | 120 ++--- src/core/BinDiff.h | 10 +- src/core/Cutter.h | 2 +- src/core/CutterDiff.cpp | 77 ++- src/core/CutterDiff.h | 49 +- src/core/MainWindow.cpp | 12 +- src/tools/bindiff/CutterDiffWindow.cpp | 244 ++++++++- src/tools/bindiff/CutterDiffWindow.h | 15 + src/tools/bindiff/CutterDiffWindow.ui | 689 ++++++++++++++++++++++++- src/tools/bindiff/HexDiff.cpp | 178 +------ src/tools/bindiff/HexDiff.h | 10 +- src/widgets/HexdumpWidget.ui | 2 +- 12 files changed, 1149 insertions(+), 259 deletions(-) diff --git a/src/core/BinDiff.cpp b/src/core/BinDiff.cpp index d1b7f1ca75..bd2e819b2b 100644 --- a/src/core/BinDiff.cpp +++ b/src/core/BinDiff.cpp @@ -6,10 +6,11 @@ bool BinDiff::threadCallback(const size_t nLeft, const size_t nMatch, void *user return bdiff->updateProgress(nLeft, nMatch); } -BinDiff::BinDiff() +BinDiff::BinDiff(CutterDiff *cutterDiff) : result(nullptr), continueRun(true), - maxTotal(1) + maxTotal(1), + cutterDiff(cutterDiff) #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) , mutex(QMutex::Recursive) @@ -19,7 +20,7 @@ BinDiff::BinDiff() BinDiff::~BinDiff() { - // rz_analysis_match_result_free(result); + rz_analysis_match_result_free(result); } bool BinDiff::hasData() @@ -27,10 +28,17 @@ bool BinDiff::hasData() return result != nullptr; } -void BinDiff::setFile(QString filePath) +void BinDiff::setFileA(QString filePath) { mutex.lock(); - file = filePath; + fileA = filePath; + mutex.unlock(); +} + +void BinDiff::setFileB(QString filePath) +{ + mutex.lock(); + fileB = filePath; mutex.unlock(); } @@ -57,18 +65,11 @@ void BinDiff::run() continueRun = true; maxTotal = 1; // maxTotal must be at least 1. mutex.unlock(); -<<<<<<< HEAD - - result = Core()->diffNewFile(file, level, compareLogic, threadCallback, this); - - Core()->diffData.setAnalysisMatchResult(result); -======= cutterDiff->initCores(); cutterDiff->openFiles(fileA, fileB); cutterDiff->analyzeCores(level); cutterDiff->syncConfig(); result = cutterDiff->matchFunctions(compareLogic, threadCallback, this); ->>>>>>> 587a4b6e (Cutter Diff) mutex.lock(); const bool canComplete = continueRun; @@ -85,20 +86,6 @@ void BinDiff::cancel() mutex.unlock(); } -<<<<<<< HEAD -// static void setFunctionDescription(FunctionDescription *desc, const RzAnalysisFunction *func) -// { -// desc->offset = func->addr; -// desc->linearSize = rz_analysis_function_linear_size(const_cast(func)); -// desc->nargs = rz_analysis_arg_count(const_cast(func)); -// desc->nlocals = rz_analysis_var_local_count(const_cast(func)); -// desc->nbbs = rz_pvector_len(func->bbs); -// desc->calltype = func->cc ? QString::fromUtf8(func->cc) : QString(); -// desc->name = func->name ? QString::fromUtf8(func->name) : QString(); -// desc->edges = rz_analysis_function_count_edges(func, nullptr); -// desc->stackframe = func->maxstack; -// } -======= static void setFunctionDescription(FunctionDescription *desc, const RzAnalysisFunction *func) { desc->offset = func->addr; @@ -111,56 +98,55 @@ static void setFunctionDescription(FunctionDescription *desc, const RzAnalysisFu desc->edges = rz_analysis_function_count_edges(func, nullptr); desc->stackframe = func->maxstack; } ->>>>>>> a8934965 (PR draft commit) - -// QList BinDiff::matches() -// { -// QList pairs; -// const RzAnalysisMatchPair *pair = nullptr; -// const RzListIter *it = nullptr; -// const RzAnalysisFunction *fcnA = nullptr; -// const RzAnalysisFunction *fcnB = nullptr; -// if (!result) { -// return pairs; -// } +QList BinDiff::matches() +{ + QList pairs; + const RzAnalysisMatchPair *pair = nullptr; + const RzListIter *it = nullptr; + const RzAnalysisFunction *fcnA = nullptr; + const RzAnalysisFunction *fcnB = nullptr; + + if (!result) { + return pairs; + } -// CutterRzListForeach (result->matches, it, RzAnalysisMatchPair, pair) { -// BinDiffMatchDescription desc; -// fcnA = static_cast(pair->pair_a); -// fcnB = static_cast(pair->pair_b); + CutterRzListForeach (result->matches, it, RzAnalysisMatchPair, pair) { + BinDiffMatchDescription desc; + fcnA = static_cast(pair->pair_a); + fcnB = static_cast(pair->pair_b); -// setFunctionDescription(&desc.original, fcnA); -// setFunctionDescription(&desc.modified, fcnB); + setFunctionDescription(&desc.original, fcnA); + setFunctionDescription(&desc.modified, fcnB); -// desc.simtype = RZ_ANALYSIS_SIMILARITY_TYPE_STR(pair->similarity); -// desc.similarity = pair->similarity; + desc.simtype = RZ_ANALYSIS_SIMILARITY_TYPE_STR(pair->similarity); + desc.similarity = pair->similarity; -// pairs.push_back(desc); -// } + pairs.push_back(desc); + } -// return pairs; -// } + return pairs; +} -// QList BinDiff::mismatch(bool originalFile) -// { -// QList list; -// if (!result) { -// return list; -// } +QList BinDiff::mismatch(bool originalFile) +{ + QList list; + if (!result) { + return list; + } -// const RzAnalysisFunction *func = nullptr; -// const RzList *unmatch = originalFile ? result->unmatch_a : result->unmatch_b; -// const RzListIter *it = nullptr; + const RzAnalysisFunction *func = nullptr; + const RzList *unmatch = originalFile ? result->unmatch_a : result->unmatch_b; + const RzListIter *it = nullptr; -// CutterRzListForeach (unmatch, it, RzAnalysisFunction, func) { -// FunctionDescription desc; -// setFunctionDescription(&desc, func); -// list.push_back(desc); -// } + CutterRzListForeach (unmatch, it, RzAnalysisFunction, func) { + FunctionDescription desc; + setFunctionDescription(&desc, func); + list.push_back(desc); + } -// return list; -// } + return list; +} bool BinDiff::updateProgress(size_t nLeft, size_t nMatch) { @@ -178,7 +164,7 @@ bool BinDiff::updateProgress(size_t nLeft, size_t nMatch) status.nMatch = nMatch; emit progress(status); - bool ret = continueRun; + const bool ret = continueRun; mutex.unlock(); return ret; } diff --git a/src/core/BinDiff.h b/src/core/BinDiff.h index 99555d8758..d11da5c1c9 100644 --- a/src/core/BinDiff.h +++ b/src/core/BinDiff.h @@ -3,6 +3,7 @@ #include "Cutter.h" #include "CutterDescriptions.h" +#include "CutterDiff.h" #include #include @@ -14,12 +15,13 @@ class BinDiff : public QThread Q_OBJECT public: - explicit BinDiff(); + explicit BinDiff(CutterDiff *cutterDiff); virtual ~BinDiff(); void run(); - void setFile(QString filePth); + void setFileA(QString filePth); + void setFileB(QString filePth); void setAnalysisLevel(int aLevel); void setCompareLogic(int cLogic); bool hasData(); @@ -35,6 +37,7 @@ public slots: void complete(); private: + CutterDiff *cutterDiff; RzAnalysisMatchResult *result; bool continueRun; size_t maxTotal; @@ -43,7 +46,8 @@ public slots: #else QRecursiveMutex mutex; #endif - QString file; + QString fileA; + QString fileB; int level; int compareLogic; diff --git a/src/core/Cutter.h b/src/core/Cutter.h index f57f2eabd7..14b37caa3f 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -145,7 +145,7 @@ class CUTTER_EXPORT CutterCore : public QObject /** * @brief a wrapper around cmdRaw(const char *cmd,). */ - QString cmdRaw(const QString &cmd) { return cmdRaw(cmd.toUtf8().constData()); }; + QString cmdRaw(const QString &cmd) { return cmdRaw(cmd.toUtf8().constData()); } /** * @brief Execute a Rizin command \a cmd at \a address. The function will preform a silent seek diff --git a/src/core/CutterDiff.cpp b/src/core/CutterDiff.cpp index 41ed748b47..55b506fdd3 100644 --- a/src/core/CutterDiff.cpp +++ b/src/core/CutterDiff.cpp @@ -4,7 +4,7 @@ #include -#define LOCK() const QMutexLocker locker(&mutex) +#define LOCK() const CutterDiffLocked lock(this); CutterDiff::CutterDiff(QObject *parent) : QObject { parent } @@ -308,5 +308,78 @@ bool CutterDiff::cmpBytesAt(RVA addrA, RVA addrB, size_t len) qWarning() << "Read failed"; return false; }; - return bufA.compare(bufB) == 0; + return bufA == bufB; +} + +QString CutterDiff::getCommonConfig(const char *k) +{ + LOCK(); + return { rz_config_get(coreA->config, k) }; +} + +void CutterDiff::setCommonConfig(const char *k, const char *v) +{ + LOCK(); + rz_config_set(coreA->config, k, v); + rz_config_set(coreB->config, k, v); +} + +int CutterDiff::getCommonConfigi(const char *k) +{ + LOCK(); + return static_cast(rz_config_get_i(coreA->config, k)); +} + +bool CutterDiff::getCommonConfigb(const char *k) +{ + LOCK(); + return rz_config_get_b(coreA->config, k); +} + +void CutterDiff::setCommonConfigb(const char *k, bool value) +{ + LOCK(); + rz_config_set_b(coreA->config, k, value); + rz_config_set_b(coreB->config, k, value); +} + +void CutterDiff::setCommonConfigi(const char *k, int v) +{ + LOCK(); + rz_config_set_i(coreA->config, k, static_cast(v)); + rz_config_set_i(coreB->config, k, static_cast(v)); +} + +void CutterDiff::seekSilent(ut64 offset, bool orig) +{ + LOCK(); + if (offset == RVA_INVALID) { + return; + } + rz_core_seek(orig ? coreA : coreB, offset, true); +} + +QString CutterDiff::cmdRawAt(const char *cmd, RVA address, bool orig) +{ + LOCK(); + QString res; + const RVA oldOffset = getOffset(orig); + seekSilent(address, orig); + + res = cmdRaw(cmd, orig); + + seekSilent(oldOffset, orig); + return res; +} + +QString CutterDiff::cmdRaw(const char *cmd, bool orig) +{ + LOCK(); + const QString res; + return rz_core_cmd_str(orig ? coreA : coreB, cmd); +} + +RVA CutterDiff::getOffset(bool orig) +{ + return (orig ? coreA : coreB)->offset; } diff --git a/src/core/CutterDiff.h b/src/core/CutterDiff.h index b1e73fbff2..636e5a5a92 100644 --- a/src/core/CutterDiff.h +++ b/src/core/CutterDiff.h @@ -6,13 +6,16 @@ #include "RizinCpp.h" #include +#include #include -class RzDiffCoreLocked; +class CutterDiffLocked; class CUTTER_EXPORT CutterDiff : public QObject { Q_OBJECT + friend class CutterDiffLocked; + public: explicit CutterDiff(QObject *parent = nullptr); ~CutterDiff(); @@ -30,11 +33,28 @@ class CUTTER_EXPORT CutterDiff : public QObject CompareLogicSymbols, ///< Only symbols }; QList getFunctionList(bool orig = true); - QByteArray ioRead(RVA addr, int len, bool orig); - RzList *getFunctions(RzAnalysis *analysis, int compareLogic); - QString getCommentAt(RVA addr, bool orig); - QString listFlagsAsStringAt(RVA addr, bool orig); + QByteArray ioRead(RVA addr, int len, bool orig = true); + QString getCommentAt(RVA addr, bool orig = true); + QString listFlagsAsStringAt(RVA addr, bool orig = true); bool cmpBytesAt(RVA addrA, RVA addrB, size_t len); + QString getString(RVA addr, uint64_t len, RzStrEnc encoding, bool escape_nl = false, + bool orig = true); + QString getCommonConfig(const char *key); + QString getCommonConfig(const QString &key) + { + return getCommonConfig(key.toUtf8().constData()); + } + void setCommonConfig(const char *key, const char *value); + int getCommonConfigi(const char *key); + int getCommonConfigi(const QString &key) { return getCommonConfigi(key.toUtf8().constData()); } + bool getCommonConfigb(const char *key); + bool getCommonConfigb(const QString &key) { return getCommonConfigb(key.toUtf8().constData()); } + void setCommonConfigi(const char *key, int value); + void setCommonConfigb(const char *key, bool value); + void seekSilent(ut64 offset, bool orig); + QString cmdRawAt(const char *cmd, RVA address, bool orig); + QString cmdRaw(const char *cmd, bool orig); + RVA getOffset(bool orig); private: RzCore *coreA = nullptr; @@ -44,7 +64,26 @@ class CUTTER_EXPORT CutterDiff : public QObject #else QRecursiveMutex mutex; #endif + RzList *getFunctions(RzAnalysis *analysis, int compareLogic); signals: }; +class CutterDiffLocked +{ + CutterDiff *const diff; + +public: + RzCore *const coreA; + RzCore *const coreB; + explicit CutterDiffLocked(CutterDiff *diff) : diff(diff), coreA(diff->coreA), coreB(diff->coreB) + { + assert(diff); + diff->mutex.lock(); + } + ~CutterDiffLocked() { diff->mutex.unlock(); } + CutterDiff *operator->() & { return diff; } + CutterDiff *operator->() const & { return diff; } + CutterDiff *operator->() && = delete; +}; + #endif // CUTTERDIFF_H diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp index 5e5280aa44..2974d8d010 100644 --- a/src/core/MainWindow.cpp +++ b/src/core/MainWindow.cpp @@ -115,11 +115,7 @@ // Tools #include "tools/basefind/BaseFindDialog.h" -<<<<<<< HEAD -#include "tools/bindiff/DiffLoadDialog.h" -======= #include "tools/bindiff/CutterDiffWindow.h" ->>>>>>> 587a4b6e (Cutter Diff) template T *getNewInstance(MainWindow *m) @@ -1756,12 +1752,8 @@ void MainWindow::onActionRefreshPanelsTriggered() void MainWindow::onActionDiffFilesTriggered() { -<<<<<<< HEAD - auto diffFilesDialog = new DiffLoadDialog(this); -======= - auto diffFilesDialog = new CutterDiffWindow(); ->>>>>>> 587a4b6e (Cutter Diff) - diffFilesDialog->show(); + auto cutterDiffWindow = new CutterDiffWindow(); + cutterDiffWindow->show(); } void MainWindow::onActionAnalyzeTriggered() const diff --git a/src/tools/bindiff/CutterDiffWindow.cpp b/src/tools/bindiff/CutterDiffWindow.cpp index 4f984b573e..2e0b158ec1 100644 --- a/src/tools/bindiff/CutterDiffWindow.cpp +++ b/src/tools/bindiff/CutterDiffWindow.cpp @@ -3,6 +3,9 @@ #include "DiffLoadDialog.h" #include "ui_CutterDiffWindow.h" +#include +#include + #include FunctionListModel::FunctionListModel(QList *list, QObject *parent) @@ -321,14 +324,15 @@ CutterDiffWindow::CutterDiffWindow(QWidget *parent) { ui->setupUi(this); ui->splitter->setSizes({ 250, 750 }); - ui->splitter->setStretchFactor(0, 1); - ui->splitter->setStretchFactor(1, 3); + ui->splitterHexView->setSizes({ 750, 250 }); cutterDiff->initCores(); connect(bDiff, &BinDiff::complete, this, &CutterDiffWindow::onBinDiffCompleted); connect(ui->actionDiffNewFiles, &QAction::triggered, this, &CutterDiffWindow::onActionDiffNewFile); - ui->tabWidget2->hide(); + syntaxHighLighter = Config()->createSyntaxHighlighter(ui->hexDisasTextEdit->document()); + ui->tabParsing->hide(); setupFonts(); + showMaximized(); } CutterDiffWindow::~CutterDiffWindow() @@ -395,7 +399,42 @@ void CutterDiffWindow::onBinDiffCompleted() connect(ui->shiftDownA, &QPushButton::clicked, this, [this]() { hexDiff->transpose(1, 0); }); connect(ui->shiftUpB, &QPushButton::clicked, this, [this]() { hexDiff->transpose(0, -1); }); connect(ui->shiftDownB, &QPushButton::clicked, this, [this]() { hexDiff->transpose(0, 1); }); - ui->tabWidget2->show(); + ui->tabParsing->show(); + + initParsing(); + + // Parsing + + // Info + + ui->copyMD5A->setIcon(QIcon(":/img/icons/copy.svg")); + ui->copySHA1A->setIcon(QIcon(":/img/icons/copy.svg")); + ui->copySHA256A->setIcon(QIcon(":/img/icons/copy.svg")); + ui->copyCRC32A->setIcon(QIcon(":/img/icons/copy.svg")); + + ui->copyMD5B->setIcon(QIcon(":/img/icons/copy.svg")); + ui->copySHA1B->setIcon(QIcon(":/img/icons/copy.svg")); + ui->copySHA256B->setIcon(QIcon(":/img/icons/copy.svg")); + ui->copyCRC32B->setIcon(QIcon(":/img/icons/copy.svg")); + + // Setup Placeholders + const QString placeholder = tr("Select bytes to display information"); + + ui->bytesMD5A->setPlaceholderText(placeholder); + ui->bytesEntropyA->setPlaceholderText(placeholder); + ui->bytesSHA1A->setPlaceholderText(placeholder); + ui->bytesSHA256A->setPlaceholderText(placeholder); + ui->bytesCRC32A->setPlaceholderText(placeholder); + ui->hexDisasTextEdit->setPlaceholderText(placeholder); + + ui->bytesMD5B->setPlaceholderText(placeholder); + ui->bytesEntropyB->setPlaceholderText(placeholder); + ui->bytesSHA1B->setPlaceholderText(placeholder); + ui->bytesSHA256B->setPlaceholderText(placeholder); + ui->bytesCRC32B->setPlaceholderText(placeholder); + + // HexDiff signals + connect(hexDiff, &HexDiff::selectionChanged, this, &CutterDiffWindow::selectionChanged); } void CutterDiffWindow::onActionDiffNewFile() @@ -413,3 +452,200 @@ void CutterDiffWindow::addHexDiff() const QFont font = Config()->getFont(); hexDiff->setMonospaceFont(font); } + +void CutterDiffWindow::onCopyMD5AClicked() +{ + const QString md5A = ui->bytesMD5A->text(); + QApplication::clipboard()->setText(md5A); +} + +void CutterDiffWindow::onCopyShA1AClicked() +{ + const QString sha1A = ui->bytesSHA1A->text(); + QApplication::clipboard()->setText(sha1A); +} + +void CutterDiffWindow::onCopyShA256AClicked() +{ + + const QString sha256A = ui->bytesSHA256A->text(); + QApplication::clipboard()->setText(sha256A); +} + +void CutterDiffWindow::onCopyCrC32AClicked() +{ + const QString crc32A = ui->bytesCRC32A->text(); + QApplication::clipboard()->setText(crc32A); +} + +void CutterDiffWindow::onCopyMD5BClicked() +{ + const QString md5B = ui->bytesMD5B->text(); + QApplication::clipboard()->setText(md5B); +} + +void CutterDiffWindow::onCopyShA1BClicked() +{ + const QString sha1B = ui->bytesSHA1B->text(); + QApplication::clipboard()->setText(sha1B); +} + +void CutterDiffWindow::onCopyShA256BClicked() +{ + + const QString sha256B = ui->bytesSHA256B->text(); + QApplication::clipboard()->setText(sha256B); +} + +void CutterDiffWindow::onCopyCrC32BClicked() +{ + const QString crc32B = ui->bytesCRC32B->text(); + QApplication::clipboard()->setText(crc32B); +} + +void CutterDiffWindow::selectionChanged(HexDiff::Selection selection) +{ + if (selection.empty) { + clearParseWindow(); + } else { + updateParseWindow(selection); + } +} + +void CutterDiffWindow::updateParseWindow(HexDiff::Selection selection) +{ + const int size = selection.endAddress - selection.startAddress + 1; + if (ui->tabParsing->currentIndex() == 1) { + const CutterDiffLocked cutterDiff(this->cutterDiff); + // scope for TempConfig + + // Get selected combos + const QString arch = ui->parseArchComboBox->currentText(); + const QString bits = ui->parseBitsComboBox->currentText(); + const QString selectedCommand = ui->parseTypeComboBox->currentData().toString(); + const QString commandResult = ""; + const bool bigEndian = ui->parseEndianComboBox->currentIndex() == 1; + const QString oldArch = cutterDiff->getCommonConfig("asm.arch"); + const QString oldBits = cutterDiff->getCommonConfig("asm.bits"); + const int oldEndian = cutterDiff->getCommonConfigi("ctf.bigendian"); + cutterDiff->setCommonConfig("asm.arch", arch.toUtf8().constData()); + cutterDiff->setCommonConfig("asm.bits", bits.toUtf8().constData()); + cutterDiff->setCommonConfigi("ctf.bigendian", bigEndian); + ui->hexDisasTextEdit->setPlainText( + selectedCommand != "" ? cutterDiff->cmdRawAt(QString("%1 @! %2") + .arg(selectedCommand) + .arg(size) + .toUtf8() + .constData(), + selection.startAddress, true) + : ""); + cutterDiff->setCommonConfig("asm.arch", oldArch.toUtf8().constData()); + cutterDiff->setCommonConfig("asm.bits", oldBits.toUtf8().constData()); + cutterDiff->setCommonConfigi("ctf.bigendian", oldEndian); + } else if (ui->tabParsing->currentIndex() == 2) { + RzHashSize digestSize = 0; + const CutterDiffLocked cutterDiff(this->cutterDiff); + const ut64 oldOffsetA = cutterDiff.coreA->offset; + const ut64 oldOffsetB = cutterDiff.coreB->offset; + rz_core_seek(cutterDiff.coreA, selection.startAddress, true); + rz_core_seek(cutterDiff.coreB, selection.startAddressB, true); + const ut8 *blockA = cutterDiff.coreA->block; + const ut8 *blockB = cutterDiff.coreB->block; + char *digest = rz_hash_cfg_calculate_small_block_string(cutterDiff.coreA->hash, "md5", + blockA, size, &digestSize, false); + ui->bytesMD5A->setText(QString(digest)); + free(digest); + + digest = rz_hash_cfg_calculate_small_block_string(cutterDiff.coreB->hash, "md5", blockB, + size, &digestSize, false); + ui->bytesMD5B->setText(QString(digest)); + free(digest); + + digest = rz_hash_cfg_calculate_small_block_string(cutterDiff.coreA->hash, "sha1", blockA, + size, &digestSize, false); + ui->bytesSHA1A->setText(QString(digest)); + free(digest); + + digest = rz_hash_cfg_calculate_small_block_string(cutterDiff.coreB->hash, "sha1", blockB, + size, &digestSize, false); + ui->bytesSHA1B->setText(QString(digest)); + free(digest); + + digest = rz_hash_cfg_calculate_small_block_string(cutterDiff.coreA->hash, "sha256", blockA, + size, &digestSize, false); + ui->bytesSHA256A->setText(QString(digest)); + free(digest); + + digest = rz_hash_cfg_calculate_small_block_string(cutterDiff.coreB->hash, "sha256", blockB, + size, &digestSize, false); + ui->bytesSHA256B->setText(QString(digest)); + free(digest); + + digest = rz_hash_cfg_calculate_small_block_string(cutterDiff.coreA->hash, "crc32", blockA, + size, &digestSize, false); + ui->bytesCRC32A->setText(QString(digest)); + free(digest); + + digest = rz_hash_cfg_calculate_small_block_string(cutterDiff.coreB->hash, "crc32", blockB, + size, &digestSize, false); + ui->bytesCRC32B->setText(QString(digest)); + free(digest); + + digest = rz_hash_cfg_calculate_small_block_string(cutterDiff.coreA->hash, "entropy", blockA, + size, &digestSize, false); + ui->bytesEntropyA->setText(QString(digest)); + free(digest); + + digest = rz_hash_cfg_calculate_small_block_string(cutterDiff.coreB->hash, "entropy", blockB, + size, &digestSize, false); + ui->bytesEntropyB->setText(QString(digest)); + free(digest); + + rz_core_seek(cutterDiff.coreA, oldOffsetA, true); + rz_core_seek(cutterDiff.coreB, oldOffsetB, true); + ui->bytesMD5A->setCursorPosition(0); + ui->bytesSHA1A->setCursorPosition(0); + ui->bytesSHA256A->setCursorPosition(0); + ui->bytesCRC32A->setCursorPosition(0); + ui->bytesMD5B->setCursorPosition(0); + ui->bytesSHA1B->setCursorPosition(0); + ui->bytesSHA256B->setCursorPosition(0); + ui->bytesCRC32B->setCursorPosition(0); + } +} + +void CutterDiffWindow::initParsing() +{ + // Fill the plugins combo for the hexdump sidebar + ui->parseTypeComboBox->addItem(tr("Disassembly"), "pda"); + ui->parseTypeComboBox->addItem(tr("String"), "pcs"); + ui->parseTypeComboBox->addItem(tr("Assembler"), "pca"); + ui->parseTypeComboBox->addItem(tr("C bytes"), "pc"); + ui->parseTypeComboBox->addItem(tr("C half-words (2 byte)"), "pch"); + ui->parseTypeComboBox->addItem(tr("C words (4 byte)"), "pcw"); + ui->parseTypeComboBox->addItem(tr("C dwords (8 byte)"), "pcd"); + ui->parseTypeComboBox->addItem(tr("Python"), "pcp"); + ui->parseTypeComboBox->addItem(tr("JSON"), "pcj"); + ui->parseTypeComboBox->addItem(tr("JavaScript"), "pcJ"); + ui->parseTypeComboBox->addItem(tr("Yara"), "pcy"); + + ui->parseArchComboBox->insertItems(0, Core()->getAsmPluginNames()); + + ui->parseEndianComboBox->setCurrentIndex(Core()->getConfigb("cfg.bigendian") ? 1 : 0); +} + +void CutterDiffWindow::clearParseWindow() +{ + ui->hexDisasTextEdit->setPlainText(""); + ui->bytesEntropyA->setText(""); + ui->bytesMD5A->setText(""); + ui->bytesSHA1A->setText(""); + ui->bytesSHA256A->setText(""); + ui->bytesCRC32A->setText(""); + + ui->bytesEntropyB->setText(""); + ui->bytesMD5B->setText(""); + ui->bytesSHA1B->setText(""); + ui->bytesSHA256B->setText(""); + ui->bytesCRC32B->setText(""); +} diff --git a/src/tools/bindiff/CutterDiffWindow.h b/src/tools/bindiff/CutterDiffWindow.h index 05423828a9..3087756018 100644 --- a/src/tools/bindiff/CutterDiffWindow.h +++ b/src/tools/bindiff/CutterDiffWindow.h @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -119,6 +120,16 @@ class CutterDiffWindow : public QMainWindow public slots: void onBinDiffCompleted(); void onActionDiffNewFile(); +private slots: + void onCopyMD5AClicked(); + void onCopyShA1AClicked(); + void onCopyShA256AClicked(); + void onCopyCrC32AClicked(); + void onCopyMD5BClicked(); + void onCopyShA1BClicked(); + void onCopyShA256BClicked(); + void onCopyCrC32BClicked(); + void selectionChanged(HexDiff::Selection selection); private: Ui::CutterDiffWindow *ui; @@ -147,6 +158,10 @@ public slots: void addHexDiff(); void setupFonts(); void refreshHex(RVA addr); + void initParsing(); + void clearParseWindow(); + void updateParseWindow(HexDiff::Selection selection); + QSyntaxHighlighter *syntaxHighLighter; }; #endif // CUTTERDIFFWINDOW_H diff --git a/src/tools/bindiff/CutterDiffWindow.ui b/src/tools/bindiff/CutterDiffWindow.ui index 6083d81e4a..7d53d8ba55 100644 --- a/src/tools/bindiff/CutterDiffWindow.ui +++ b/src/tools/bindiff/CutterDiffWindow.ui @@ -6,12 +6,12 @@ 0 0 - 581 - 444 + 1080 + 720 - MainWindow + Cutter Diff @@ -136,7 +136,7 @@ - Page + Hex Diff @@ -152,10 +152,13 @@ 0 - + Qt::Orientation::Horizontal + + false + @@ -165,7 +168,7 @@ 0 - + 0 @@ -173,7 +176,7 @@ - 0 + 2 @@ -256,6 +259,676 @@ + + + Parsing + + + + + + 5 + + + 0 + + + + + 0 + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + Endian + + + + + + + QComboBox::SizeAdjustPolicy::AdjustToContents + + + + Little + + + + + Big + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Plain + + + 0 + + + + QLayout::SizeConstraint::SetMinimumSize + + + 5 + + + 0 + + + 5 + + + 5 + + + + + + 0 + 0 + + + + Arch + + + + + + + + 0 + 0 + + + + QComboBox::SizeAdjustPolicy::AdjustToContents + + + false + + + + + + + + 0 + 0 + + + + Bits + + + + + + + QComboBox::SizeAdjustPolicy::AdjustToContents + + + + 16 + + + + + 32 + + + + + 64 + + + + + + + + + + + + 0 + 0 + + + + + Anonymous Pro + 13 + + + + QFrame::Shape::NoFrame + + + 0 + + + true + + + + + + + + + + + + + Info + + + + + + INFO A + + + + + + + QLayout::SizeConstraint::SetDefaultConstraint + + + 0 + + + 8 + + + 10 + + + + + + 0 + 0 + + + + 4 + + + SHA256: + + + + + + + + 0 + 0 + + + + true + + + Qt::AlignmentFlag::AlignJustify|Qt::AlignmentFlag::AlignVCenter + + + true + + + + + + + + 0 + 0 + + + + SHA1: + + + + + + + Copy SHA256 + + + Qt::LayoutDirection::RightToLeft + + + + + + + + + + Copy SHA1 + + + Qt::LayoutDirection::RightToLeft + + + + + + + + + + true + + + Qt::AlignmentFlag::AlignJustify|Qt::AlignmentFlag::AlignVCenter + + + true + + + + + + + Copy CRC32 + + + Qt::LayoutDirection::RightToLeft + + + + + + + + + + + 0 + 0 + + + + true + + + Qt::AlignmentFlag::AlignJustify|Qt::AlignmentFlag::AlignVCenter + + + true + + + + + + + + 0 + 0 + + + + Entropy: + + + + + + + + 0 + 0 + + + + true + + + Qt::AlignmentFlag::AlignJustify|Qt::AlignmentFlag::AlignVCenter + + + true + + + + + + + + 0 + 0 + + + + MD5: + + + + + + + + 0 + 0 + + + + CRC32: + + + + + + + Copy MD5 + + + Qt::LayoutDirection::RightToLeft + + + + + + + + + + true + + + Qt::AlignmentFlag::AlignJustify|Qt::AlignmentFlag::AlignVCenter + + + true + + + + + + + + + INFO B + + + + + + + QLayout::SizeConstraint::SetDefaultConstraint + + + 0 + + + 8 + + + 10 + + + + + + 0 + 0 + + + + 4 + + + SHA256: + + + + + + + + 0 + 0 + + + + true + + + Qt::AlignmentFlag::AlignJustify|Qt::AlignmentFlag::AlignVCenter + + + true + + + + + + + + 0 + 0 + + + + SHA1: + + + + + + + Copy SHA256 + + + Qt::LayoutDirection::RightToLeft + + + + + + + + + + Copy SHA1 + + + Qt::LayoutDirection::RightToLeft + + + + + + + + + + true + + + Qt::AlignmentFlag::AlignJustify|Qt::AlignmentFlag::AlignVCenter + + + true + + + + + + + Copy CRC32 + + + Qt::LayoutDirection::RightToLeft + + + + + + + + + + + 0 + 0 + + + + true + + + Qt::AlignmentFlag::AlignJustify|Qt::AlignmentFlag::AlignVCenter + + + true + + + + + + + + 0 + 0 + + + + Entropy: + + + + + + + + 0 + 0 + + + + true + + + Qt::AlignmentFlag::AlignJustify|Qt::AlignmentFlag::AlignVCenter + + + true + + + + + + + + 0 + 0 + + + + MD5: + + + + + + + + 0 + 0 + + + + CRC32: + + + + + + + Copy MD5 + + + Qt::LayoutDirection::RightToLeft + + + + + + + + + + true + + + Qt::AlignmentFlag::AlignJustify|Qt::AlignmentFlag::AlignVCenter + + + true + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + @@ -274,7 +947,7 @@ 0 0 - 581 + 1080 22 diff --git a/src/tools/bindiff/HexDiff.cpp b/src/tools/bindiff/HexDiff.cpp index 882c634025..6e9531d6c0 100644 --- a/src/tools/bindiff/HexDiff.cpp +++ b/src/tools/bindiff/HexDiff.cpp @@ -51,7 +51,6 @@ HexDiff::HexDiff(CutterDiff *cutterDiff, QWidget *parent) showAscii(true), showExHex(true), showExAddr(true), - warningTimer(this), vScrollBar(new AddressRangeScrollBar(this)), cutterDiff(cutterDiff) { @@ -169,9 +168,6 @@ HexDiff::HexDiff(CutterDiff *cutterDiff, QWidget *parent) cursor.startBlinking(); updateColors(); - - warningTimer.setSingleShot(true); - connect(&warningTimer, &QTimer::timeout, this, &HexDiff::hideWarningRect); } void HexDiff::setMonospaceFont(const QFont &font) @@ -249,7 +245,7 @@ void HexDiff::updateCounts() { actionHexPairs->setEnabled(rowSizeBytes > 1 && itemByteLen == 1 && itemFormat == ItemFormat::ItemFormatHex); - actionHexPairs->setChecked(Core()->getConfigb("hex.pairs")); + actionHexPairs->setChecked(cutterDiff->getCommonConfigb("hex.pairs")); if (actionHexPairs->isChecked() && actionHexPairs->isEnabled()) { itemGroupSize = 2; } else { @@ -341,7 +337,8 @@ void HexDiff::clearSelection() HexDiff::Selection HexDiff::getSelection() { - return Selection { selection.isEmpty(), selection.start(), selection.end() }; + return Selection { selection.isEmpty(), selection.start(), selection.end(), + getAddressB(selection.start()), getAddressB(selection.end()) }; } void HexDiff::seek(uint64_t address, bool orig) @@ -428,11 +425,6 @@ void HexDiff::paintEvent(QPaintEvent *event) drawAsciiArea(painter, ctxB); drawAddrArea(painter, ctxB); - if (warningRectVisible) { - painter.setPen(warningColor); - painter.drawRect(warningRect); - } - if (!cursorEnabled) { return; } @@ -500,23 +492,6 @@ void HexDiff::mouseMoveEvent(QMouseEvent *event) infoText = metaData.replace(",", ", "); } - const auto marks = Core()->getMarksAt(mouseAddr); - for (const auto &mark : marks) { - if (mark.realname.isEmpty()) { - continue; - } - if (!infoText.isEmpty()) { - infoText += "
"; - } - const QColor c = mark.color; - infoText += QString("● " - " %5") - .arg(c.red()) - .arg(c.green()) - .arg(c.blue()) - .arg(markAlphaF) - .arg(mark.realname.toHtmlEscaped()); - } if (!infoText.isEmpty()) { // forces tooltip to follow cursor movement QToolTip::showText(mapToGlobal(event->pos()), infoText + " ", this); @@ -665,7 +640,7 @@ bool HexDiff::event(QEvent *event) } void HexDiff::keyPressEvent(QKeyEvent *event) -{ +{ // Navigation mode has to be rechecked bool select = false; auto moveOrSelect = [event, &select](QKeySequence::StandardKey moveSeq, QKeySequence::StandardKey selectSeq) -> bool { @@ -792,7 +767,7 @@ void HexDiff::onCursorBlinked() void HexDiff::onHexPairsModeEnabled(bool enable) { // Sync configuration - Core()->setConfig("hex.pairs", enable); + cutterDiff->setCommonConfigb("hex.pairs", enable); if (enable) { setItemGroupSize(2); } else { @@ -805,16 +780,33 @@ void HexDiff::copy() if (selection.isEmpty() || selection.size() > maxCopySize) { return; } - - auto x = cursorArea < 2 - ? Core()->getString(selection.start(), selection.size(), RZ_STRING_ENC_8BIT, true) - : Core()->ioRead(selection.start(), (int)selection.size()).toHex(); + QString x; + qInfo() << cursorArea; + if (cursorArea < 2) { + if (cursorArea == DiffArea::AsciiA) { + x = QString::fromUtf8( + cutterDiff->ioRead(selection.start(), (int)selection.size(), true)); + } else { + x = QString::fromUtf8(cutterDiff->ioRead(getAddressB(selection.start()), + (int)selection.size(), false)); + } + } else { + if (cursorArea == DiffArea::ItemA) { + x = cutterDiff->ioRead(selection.start(), (int)selection.size(), true).toHex(); + } else { + x = cutterDiff->ioRead(getAddressB(selection.start()), (int)selection.size(), false) + .toHex(); + } + } QApplication::clipboard()->setText(x); } void HexDiff::copyAddress() { - const uint64_t addr = getLocationAddress(); + uint64_t addr = getLocationAddress(); + if (cursorArea % 2) { + addr = getAddressB(addr); + } QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(rzAddressString(addr)); } @@ -1128,7 +1120,7 @@ void HexDiff::fillSelectionBackground(QPainter &painter, DiffFileContext &ctx, b rangePolygons(ctxSelection(ctx).start(), ctxSelection(ctx).end(), ascii, ctx); for (const auto &shape : parts) { const QColor highlightColor = palette().color(QPalette::Highlight); - if (ascii == cursorArea < 2) { + if (ascii == (cursorArea < 2)) { painter.setBrush(highlightColor); painter.drawPolygon(shape); } else { @@ -1639,106 +1631,6 @@ QString HexDiff::getFlagsAndComment(uint64_t address, DiffFileContext &ctx) return metaData; } -template -static bool checkRange(BigValue v) -{ - return v >= std::numeric_limits::min() && v <= std::numeric_limits::max(); -} - -template -static bool checkAndWrite(BigInteger value, uint8_t *buf, bool littleEndian) -{ - if (!checkRange(value)) { - return false; - } - if (littleEndian) { - qToLittleEndian((T)value, buf); - } else { - qToBigEndian((T)value, buf); - } - return true; -} - -template -static bool checkAndWriteWithSign(const QVariant &value, uint8_t *buf, bool isSigned, - bool littleEndian) -{ - if (isSigned) { - return checkAndWrite(value.toLongLong(), buf, littleEndian); - } else { - return checkAndWrite(value.toULongLong(), buf, littleEndian); - } -} - -bool HexDiff::parseWord(const QString &word, uint8_t *buf, size_t bufferSize) const -{ - bool parseOk = false; - if (bufferSize < size_t(itemByteLen)) { - return false; - } - if (itemFormat == ItemFormatFloat) { - if (itemByteLen == 4) { - const float value = word.toFloat(&parseOk); - if (!parseOk) { - return false; - } - if (itemBigEndian) { - rz_write_be_float(buf, value); - } else { - rz_write_le_float(buf, value); - } - return true; - } else if (itemByteLen == 8) { - const double value = word.toDouble(&parseOk); - if (!parseOk) { - return false; - } - if (itemBigEndian) { - rz_write_be_double(buf, value); - } else { - rz_write_le_double(buf, value); - } - return true; - } - return false; - } else { - QVariant value; - bool isSigned = false; - switch (itemFormat) { - case ItemFormatHex: - value = word.toULongLong(&parseOk, 16); - break; - case ItemFormatOct: - value = word.toULongLong(&parseOk, 8); - break; - case ItemFormatDec: - value = word.toULongLong(&parseOk, 10); - break; - case ItemFormatSignedDec: - isSigned = true; - value = word.toLongLong(&parseOk, 10); - break; - default: - break; - } - if (!parseOk) { - return false; - } - - switch (itemByteLen) { - case 1: - return checkAndWriteWithSign(value, buf, isSigned, !itemBigEndian); - case 2: - return checkAndWriteWithSign(value, buf, isSigned, !itemBigEndian); - case 4: - return checkAndWriteWithSign(value, buf, isSigned, !itemBigEndian); - case 8: - return checkAndWriteWithSign(value, buf, isSigned, !itemBigEndian); - } - } - return false; -} - void HexDiff::fetchData() { ctxA.data->fetch(startAddress, bytesPerScreen()); @@ -1859,20 +1751,6 @@ RVA HexDiff::getLocationAddress() return !selection.isEmpty() ? selection.start() : cursor.address; } -void HexDiff::hideWarningRect() -{ - warningRectVisible = false; - updateViewport(); -} - -void HexDiff::showWarningRect(QRectF rect) -{ - warningRect = rect; - warningRectVisible = true; - warningTimer.start(warningTimeMs); - updateViewport(); -} - void HexDiff::updateViewport() { vScrollBar->setPosition(startAddress); diff --git a/src/tools/bindiff/HexDiff.h b/src/tools/bindiff/HexDiff.h index c6fa4f1756..3c4a865a2a 100644 --- a/src/tools/bindiff/HexDiff.h +++ b/src/tools/bindiff/HexDiff.h @@ -309,6 +309,8 @@ class HexDiff : public QScrollArea bool empty; RVA startAddress; RVA endAddress; + RVA startAddressB; + RVA endAddressB; }; Selection getSelection(); public slots: @@ -447,10 +449,6 @@ private slots: bool isFixedWidth() const; HexNavigationMode defaultNavigationMode(); - bool parseWord(const QString &word, uint8_t *buf, size_t bufferSize) const; - - void hideWarningRect(); - void showWarningRect(QRectF rect); void updateViewport(); void scrollLines(int lines, bool clampToScrollBarRange = false); @@ -558,10 +556,6 @@ private slots: HexNavigationMode navigationMode = HexNavigationMode::Words; - bool warningRectVisible = false; - QRectF warningRect; - QTimer warningTimer; - CutterDiff *cutterDiff; AddressRangeScrollBar *vScrollBar; diff --git a/src/widgets/HexdumpWidget.ui b/src/widgets/HexdumpWidget.ui index 8e27de996a..fb6e6c892e 100644 --- a/src/widgets/HexdumpWidget.ui +++ b/src/widgets/HexdumpWidget.ui @@ -57,7 +57,7 @@ QTabWidget::TabPosition::North - 0 + 1 true From 7e1a4cb7df053d7adf49de4ca99cb3fc4adb534d Mon Sep 17 00:00:00 2001 From: NewtronReal Date: Tue, 23 Jun 2026 21:36:22 +0530 Subject: [PATCH 08/13] fixing double free new Commit Code notes. clang-format fixed clang-tidy errors changes clang-format --- src/core/BinDiff.cpp | 11 +- src/core/BinDiff.h | 6 +- src/core/CutterDiff.cpp | 38 ++++- src/core/CutterDiff.h | 3 + src/tools/bindiff/CutterDiffWindow.cpp | 118 +++++++-------- src/tools/bindiff/CutterDiffWindow.h | 7 +- src/tools/bindiff/CutterDiffWindow.ui | 202 +------------------------ src/tools/bindiff/DiffLoadDialog.cpp | 2 +- src/tools/bindiff/DiffWaitDialog.cpp | 28 ++-- src/tools/bindiff/DiffWaitDialog.h | 4 +- src/tools/bindiff/HexDiff.cpp | 97 ++++++------ src/tools/bindiff/HexDiff.h | 69 +++++---- 12 files changed, 207 insertions(+), 378 deletions(-) diff --git a/src/core/BinDiff.cpp b/src/core/BinDiff.cpp index bd2e819b2b..90dd19ffb9 100644 --- a/src/core/BinDiff.cpp +++ b/src/core/BinDiff.cpp @@ -7,10 +7,10 @@ bool BinDiff::threadCallback(const size_t nLeft, const size_t nMatch, void *user } BinDiff::BinDiff(CutterDiff *cutterDiff) - : result(nullptr), + : cutterDiff(cutterDiff), + result(nullptr), continueRun(true), - maxTotal(1), - cutterDiff(cutterDiff) + maxTotal(1) #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) , mutex(QMutex::Recursive) @@ -31,14 +31,14 @@ bool BinDiff::hasData() void BinDiff::setFileA(QString filePath) { mutex.lock(); - fileA = filePath; + fileA = std::move(filePath); mutex.unlock(); } void BinDiff::setFileB(QString filePath) { mutex.lock(); - fileB = filePath; + fileB = std::move(filePath); mutex.unlock(); } @@ -65,7 +65,6 @@ void BinDiff::run() continueRun = true; maxTotal = 1; // maxTotal must be at least 1. mutex.unlock(); - cutterDiff->initCores(); cutterDiff->openFiles(fileA, fileB); cutterDiff->analyzeCores(level); cutterDiff->syncConfig(); diff --git a/src/core/BinDiff.h b/src/core/BinDiff.h index d11da5c1c9..a501d5d183 100644 --- a/src/core/BinDiff.h +++ b/src/core/BinDiff.h @@ -9,7 +9,11 @@ #include #include - +/** + * @brief The BinDiff class + * Thread run for processing the functional diffing and other large diffing processes. + * TODO: Move to common or tools/bindiff? + */ class BinDiff : public QThread { Q_OBJECT diff --git a/src/core/CutterDiff.cpp b/src/core/CutterDiff.cpp index 55b506fdd3..e2b13b64b0 100644 --- a/src/core/CutterDiff.cpp +++ b/src/core/CutterDiff.cpp @@ -25,8 +25,9 @@ bool CutterDiff::initCores() { LOCK(); - rz_core_free(coreA); - rz_core_free(coreB); + if (coreA || coreB) { + qInfo() << "preInit"; + } coreA = rz_core_new(); coreB = rz_core_new(); @@ -42,6 +43,7 @@ bool CutterDiff::initCores() coreB->print->scr_prompt = false; return true; fail: + qWarning() << "Core initialization has failed undefined behaviour expected."; rz_core_free(coreA); rz_core_free(coreB); return false; @@ -383,3 +385,35 @@ RVA CutterDiff::getOffset(bool orig) { return (orig ? coreA : coreB)->offset; } + +QString CutterDiff::ansiEscapeToHtml(const QString &text) +{ + int len; + QString r = text; + r.replace("\t", " "); + char *html = rz_cons_html_filter( + r.toUtf8().constData(), + &len); // doesn't support background colors work has to be done in the rizin side + if (!html) { + return {}; + } + r = QString::fromUtf8(html, len); + rz_mem_free(html); + return r; +} + +QStringList CutterDiff::lineDiff(const char *lines1, const char *lines2) +{ + RzDiff *diff = rz_diff_lines_new(lines1, lines2, nullptr); + const char *results = rz_diff_unified_text(diff, "A", "B", false, true); + QStringList lines = QString::fromUtf8(results).split("\n"); + for (QString &line : lines) { + line = ansiEscapeToHtml(line); + } + return lines; +} + +QStringList CutterDiff::lineDiff(const QString &lines1, const QString &lines2) +{ + return lineDiff(lines1.toUtf8().constData(), lines2.toUtf8().constData()); +} diff --git a/src/core/CutterDiff.h b/src/core/CutterDiff.h index 636e5a5a92..7c44101930 100644 --- a/src/core/CutterDiff.h +++ b/src/core/CutterDiff.h @@ -55,6 +55,9 @@ class CUTTER_EXPORT CutterDiff : public QObject QString cmdRawAt(const char *cmd, RVA address, bool orig); QString cmdRaw(const char *cmd, bool orig); RVA getOffset(bool orig); + QStringList lineDiff(const char *lines1, const char *lines2); + QStringList lineDiff(const QString &lines1, const QString &lines2); + QString ansiEscapeToHtml(const QString &text); private: RzCore *coreA = nullptr; diff --git a/src/tools/bindiff/CutterDiffWindow.cpp b/src/tools/bindiff/CutterDiffWindow.cpp index 2e0b158ec1..1096460872 100644 --- a/src/tools/bindiff/CutterDiffWindow.cpp +++ b/src/tools/bindiff/CutterDiffWindow.cpp @@ -9,7 +9,7 @@ #include FunctionListModel::FunctionListModel(QList *list, QObject *parent) - : list(list), AddressableItemModel<>(parent) + : AddressableItemModel<>(parent), list(list) { } QModelIndex FunctionListModel::index(int row, int column, const QModelIndex &parent) const @@ -132,6 +132,12 @@ int DiffMatchModel::columnCount(const QModelIndex &) const return DiffMatchModel::ColumnCount; } +QPair DiffMatchModel::address(const QModelIndex &index) const +{ + return QPair(list->at(index.row()).original.offset, + list->at(index.row()).modified.offset); +} + QVariant DiffMatchModel::data(const QModelIndex &index, int role) const { if (index.row() >= list->count()) { @@ -318,18 +324,21 @@ QVariant DiffMismatchModel::headerData(int section, Qt::Orientation, int role) c CutterDiffWindow::CutterDiffWindow(QWidget *parent) : QMainWindow(parent), + ui(new Ui::CutterDiffWindow), cutterDiff(new CutterDiff), - bDiff(new BinDiff(cutterDiff)), - ui(new Ui::CutterDiffWindow) + bDiff(new BinDiff(cutterDiff)) { ui->setupUi(this); + cutterDiff->initCores(); ui->splitter->setSizes({ 250, 750 }); ui->splitterHexView->setSizes({ 750, 250 }); - cutterDiff->initCores(); + ui->treeViewMatches->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->treeViewMatches, &CutterTreeView::customContextMenuRequested, this, + &CutterDiffWindow::showContextMenuMatches); + connect(ui->treeViewMatches, &CutterTreeView::clicked, this, &CutterDiffWindow::selectFunction); connect(bDiff, &BinDiff::complete, this, &CutterDiffWindow::onBinDiffCompleted); connect(ui->actionDiffNewFiles, &QAction::triggered, this, &CutterDiffWindow::onActionDiffNewFile); - syntaxHighLighter = Config()->createSyntaxHighlighter(ui->hexDisasTextEdit->document()); ui->tabParsing->hide(); setupFonts(); showMaximized(); @@ -340,17 +349,51 @@ CutterDiffWindow::~CutterDiffWindow() delete ui; } -void CutterDiffWindow::setupFonts() {} +// Work incomplete +void CutterDiffWindow::selectFunction(const QModelIndex &index) {} -void CutterDiffWindow::refreshHex(RVA addr) +void CutterDiffWindow::showContextMenuMatches(const QPoint &pos) { - // if (addr != RVA_INVALID) { - // ui->hexDiffTextView->seek(addr); - // } else { - // ui->hexDiffTextView->refresh(); - // } + const QModelIndex index = ui->treeViewMatches->indexAt(pos); + auto addr = matches->address(index); + + QMenu menu(this); + + const QAction *seekTo = menu.addAction("Seek to"); + const QAction *goToAndAlign = menu.addAction("Align HexDiff"); + const QAction *diffFunctionLines = menu.addAction("Line Diff"); + const QAction *copyAddress = menu.addAction("Copy Address"); + + const QAction *selected = menu.exec(ui->treeViewMatches->viewport()->mapToGlobal(pos)); + + if (selected == seekTo) { + if (index.column() < DiffMatchModel::AddressMod) { + hexDiff->seek(addr.first); + } else { + hexDiff->seek(addr.second, false); + } + ui->tabWidget->setCurrentIndex(3); + } else if (selected == goToAndAlign) { + if (addr.first > addr.second) { + hexDiff->transpose(0, -static_cast(addr.first - addr.second), true); + } else { + hexDiff->transpose(0, static_cast(addr.second - addr.first), true); + } + ui->tabWidget->setCurrentIndex(3); + hexDiff->seek(addr.first); + } else if (selected == diffFunctionLines) { + // once diffLineView is implemented + } else if (selected == copyAddress) { + if (index.column() < DiffMatchModel::AddressMod) { + QApplication::clipboard()->setText(QString::number(addr.first)); + } else { + QApplication::clipboard()->setText(QString::number(addr.second)); + } + } } +void CutterDiffWindow::setupFonts() {} + void CutterDiffWindow::onBinDiffCompleted() { if (!bDiff->hasData()) { @@ -401,8 +444,6 @@ void CutterDiffWindow::onBinDiffCompleted() connect(ui->shiftDownB, &QPushButton::clicked, this, [this]() { hexDiff->transpose(0, 1); }); ui->tabParsing->show(); - initParsing(); - // Parsing // Info @@ -425,7 +466,6 @@ void CutterDiffWindow::onBinDiffCompleted() ui->bytesSHA1A->setPlaceholderText(placeholder); ui->bytesSHA256A->setPlaceholderText(placeholder); ui->bytesCRC32A->setPlaceholderText(placeholder); - ui->hexDisasTextEdit->setPlaceholderText(placeholder); ui->bytesMD5B->setPlaceholderText(placeholder); ui->bytesEntropyB->setPlaceholderText(placeholder); @@ -516,33 +556,6 @@ void CutterDiffWindow::updateParseWindow(HexDiff::Selection selection) { const int size = selection.endAddress - selection.startAddress + 1; if (ui->tabParsing->currentIndex() == 1) { - const CutterDiffLocked cutterDiff(this->cutterDiff); - // scope for TempConfig - - // Get selected combos - const QString arch = ui->parseArchComboBox->currentText(); - const QString bits = ui->parseBitsComboBox->currentText(); - const QString selectedCommand = ui->parseTypeComboBox->currentData().toString(); - const QString commandResult = ""; - const bool bigEndian = ui->parseEndianComboBox->currentIndex() == 1; - const QString oldArch = cutterDiff->getCommonConfig("asm.arch"); - const QString oldBits = cutterDiff->getCommonConfig("asm.bits"); - const int oldEndian = cutterDiff->getCommonConfigi("ctf.bigendian"); - cutterDiff->setCommonConfig("asm.arch", arch.toUtf8().constData()); - cutterDiff->setCommonConfig("asm.bits", bits.toUtf8().constData()); - cutterDiff->setCommonConfigi("ctf.bigendian", bigEndian); - ui->hexDisasTextEdit->setPlainText( - selectedCommand != "" ? cutterDiff->cmdRawAt(QString("%1 @! %2") - .arg(selectedCommand) - .arg(size) - .toUtf8() - .constData(), - selection.startAddress, true) - : ""); - cutterDiff->setCommonConfig("asm.arch", oldArch.toUtf8().constData()); - cutterDiff->setCommonConfig("asm.bits", oldBits.toUtf8().constData()); - cutterDiff->setCommonConfigi("ctf.bigendian", oldEndian); - } else if (ui->tabParsing->currentIndex() == 2) { RzHashSize digestSize = 0; const CutterDiffLocked cutterDiff(this->cutterDiff); const ut64 oldOffsetA = cutterDiff.coreA->offset; @@ -614,29 +627,8 @@ void CutterDiffWindow::updateParseWindow(HexDiff::Selection selection) } } -void CutterDiffWindow::initParsing() -{ - // Fill the plugins combo for the hexdump sidebar - ui->parseTypeComboBox->addItem(tr("Disassembly"), "pda"); - ui->parseTypeComboBox->addItem(tr("String"), "pcs"); - ui->parseTypeComboBox->addItem(tr("Assembler"), "pca"); - ui->parseTypeComboBox->addItem(tr("C bytes"), "pc"); - ui->parseTypeComboBox->addItem(tr("C half-words (2 byte)"), "pch"); - ui->parseTypeComboBox->addItem(tr("C words (4 byte)"), "pcw"); - ui->parseTypeComboBox->addItem(tr("C dwords (8 byte)"), "pcd"); - ui->parseTypeComboBox->addItem(tr("Python"), "pcp"); - ui->parseTypeComboBox->addItem(tr("JSON"), "pcj"); - ui->parseTypeComboBox->addItem(tr("JavaScript"), "pcJ"); - ui->parseTypeComboBox->addItem(tr("Yara"), "pcy"); - - ui->parseArchComboBox->insertItems(0, Core()->getAsmPluginNames()); - - ui->parseEndianComboBox->setCurrentIndex(Core()->getConfigb("cfg.bigendian") ? 1 : 0); -} - void CutterDiffWindow::clearParseWindow() { - ui->hexDisasTextEdit->setPlainText(""); ui->bytesEntropyA->setText(""); ui->bytesMD5A->setText(""); ui->bytesSHA1A->setText(""); diff --git a/src/tools/bindiff/CutterDiffWindow.h b/src/tools/bindiff/CutterDiffWindow.h index 3087756018..37c0da24c7 100644 --- a/src/tools/bindiff/CutterDiffWindow.h +++ b/src/tools/bindiff/CutterDiffWindow.h @@ -47,6 +47,8 @@ class DiffMatchModel : public QAbstractListModel QColor gradientByRatio(const double ratio) const; + QPair address(const QModelIndex &index) const; + private: QList *list; @@ -104,6 +106,7 @@ class FunctionListModel : public AddressableItemModel<> int role = Qt::DisplayRole) const override; RVA address(const QModelIndex &index) const override; void refreshModel(); + bool highLightAddress(); private: QList *list; @@ -130,6 +133,8 @@ private slots: void onCopyShA256BClicked(); void onCopyCrC32BClicked(); void selectionChanged(HexDiff::Selection selection); + void showContextMenuMatches(const QPoint &pos); + void selectFunction(const QModelIndex &index); private: Ui::CutterDiffWindow *ui; @@ -157,8 +162,6 @@ private slots: HexDiff *hexDiff = nullptr; void addHexDiff(); void setupFonts(); - void refreshHex(RVA addr); - void initParsing(); void clearParseWindow(); void updateParseWindow(HexDiff::Selection selection); QSyntaxHighlighter *syntaxHighLighter; diff --git a/src/tools/bindiff/CutterDiffWindow.ui b/src/tools/bindiff/CutterDiffWindow.ui index 7d53d8ba55..3e324d59cf 100644 --- a/src/tools/bindiff/CutterDiffWindow.ui +++ b/src/tools/bindiff/CutterDiffWindow.ui @@ -102,7 +102,7 @@ - 3 + 0 @@ -176,7 +176,7 @@ - 2 + 1 @@ -259,204 +259,6 @@ - - - Parsing - - - - - - 5 - - - 0 - - - - - 0 - - - - - - 0 - 0 - - - - - - - - - 0 - 0 - - - - Endian - - - - - - - QComboBox::SizeAdjustPolicy::AdjustToContents - - - - Little - - - - - Big - - - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - QFrame::Shape::NoFrame - - - QFrame::Shadow::Plain - - - 0 - - - - QLayout::SizeConstraint::SetMinimumSize - - - 5 - - - 0 - - - 5 - - - 5 - - - - - - 0 - 0 - - - - Arch - - - - - - - - 0 - 0 - - - - QComboBox::SizeAdjustPolicy::AdjustToContents - - - false - - - - - - - - 0 - 0 - - - - Bits - - - - - - - QComboBox::SizeAdjustPolicy::AdjustToContents - - - - 16 - - - - - 32 - - - - - 64 - - - - - - - - - - - - 0 - 0 - - - - - Anonymous Pro - 13 - - - - QFrame::Shape::NoFrame - - - 0 - - - true - - - - - - - - - - Info diff --git a/src/tools/bindiff/DiffLoadDialog.cpp b/src/tools/bindiff/DiffLoadDialog.cpp index 8ebc636f18..c67c3ef6fb 100644 --- a/src/tools/bindiff/DiffLoadDialog.cpp +++ b/src/tools/bindiff/DiffLoadDialog.cpp @@ -11,7 +11,7 @@ #include DiffLoadDialog::DiffLoadDialog(BinDiff *bDiff, QWidget *parent) - : QDialog(parent), bDiff(bDiff), ui(new Ui::DiffLoadDialog) + : QDialog(parent), ui(new Ui::DiffLoadDialog), bDiff(bDiff) { ui->setupUi(this); setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); diff --git a/src/tools/bindiff/DiffWaitDialog.cpp b/src/tools/bindiff/DiffWaitDialog.cpp index 9844e95d2a..0ba21aea0d 100644 --- a/src/tools/bindiff/DiffWaitDialog.cpp +++ b/src/tools/bindiff/DiffWaitDialog.cpp @@ -22,7 +22,7 @@ DiffWaitDialog::DiffWaitDialog(BinDiff *bDiff, QWidget *parent) ui->lineEditNFuncs->setText("0"); ui->lineEditMatches->setText("0"); - QTime zero(0, 0, 0, 0); + const QTime zero(0, 0, 0, 0); ui->lineEditElapsedTime->setText(zero.toString("hh:mm:ss")); ui->lineEditEstimatedTime->setText(zero.toString("hh:mm:ss")); } @@ -36,7 +36,7 @@ DiffWaitDialog::~DiffWaitDialog() delete bDiff; } -void DiffWaitDialog::show(QString original, QString modified, int level, int compare) +void DiffWaitDialog::show(const QString &original, const QString &modified, int level, int compare) { connect(this, &DiffWaitDialog::cancelJob, bDiff, &BinDiff::cancel); connect(bDiff, &BinDiff::progress, this, &DiffWaitDialog::onProgress); @@ -60,19 +60,19 @@ void DiffWaitDialog::show(QString original, QString modified, int level, int com void DiffWaitDialog::onProgress(BinDiffStatusDescription status) { - int partial = status.total - status.nLeft; - ut32 progress = (100 * partial) / status.total; + const int partial = status.total - status.nLeft; + const ut32 progress = (100 * partial) / status.total; ui->progressBar->setValue(progress); ui->lineEditNFuncs->setText(QString::asprintf("%lu", status.nLeft)); ui->lineEditMatches->setText(QString::asprintf("%lu", status.nMatch)); - double speed = ((double)partial) / ((double)eTimer.elapsed()); + const double speed = ((double)partial) / ((double)eTimer.elapsed()); ut64 seconds = (((double)status.nLeft) / speed) / 1000ull; - int hours = seconds / 3600; + const int hours = seconds / 3600; seconds -= (hours * 3600); - int minutes = seconds / 60; + const int minutes = seconds / 60; seconds = seconds % 60; - QTime estimated(hours, minutes, seconds, 0); + const QTime estimated(hours, minutes, seconds, 0); ui->lineEditEstimatedTime->setText(estimated.toString("hh:mm:ss")); } @@ -80,23 +80,17 @@ void DiffWaitDialog::onCompletion() { timer.stop(); - // if (bDiff->hasData()) { - // auto results = new DiffWindow(bDiff, this); - // bDiff = nullptr; - // results->showMaximized(); - // } - close(); } void DiffWaitDialog::updateElapsedTime() { ut64 seconds = eTimer.elapsed() / 1000ull; - int hours = seconds / 3600; + const int hours = seconds / 3600; seconds -= (hours * 3600); - int minutes = seconds / 60; + const int minutes = seconds / 60; seconds = seconds % 60; - QTime current(hours, minutes, seconds, 0); + const QTime current(hours, minutes, seconds, 0); ui->lineEditElapsedTime->setText(current.toString("hh:mm:ss")); } diff --git a/src/tools/bindiff/DiffWaitDialog.h b/src/tools/bindiff/DiffWaitDialog.h index 0993785a22..e99d9f43be 100644 --- a/src/tools/bindiff/DiffWaitDialog.h +++ b/src/tools/bindiff/DiffWaitDialog.h @@ -23,7 +23,7 @@ class DiffWaitDialog : public QDialog explicit DiffWaitDialog(BinDiff *bDiff, QWidget *parent = nullptr); ~DiffWaitDialog(); - void show(QString original, QString modified, int level, int compare); + void show(const QString &original, const QString &modified, int level, int compare); public slots: void onProgress(BinDiffStatusDescription status); @@ -38,8 +38,8 @@ private slots: private: QElapsedTimer eTimer; - QTimer timer; BinDiff *bDiff; + QTimer timer; std::unique_ptr ui; }; diff --git a/src/tools/bindiff/HexDiff.cpp b/src/tools/bindiff/HexDiff.cpp index 6e9531d6c0..bc1e17fa8b 100644 --- a/src/tools/bindiff/HexDiff.cpp +++ b/src/tools/bindiff/HexDiff.cpp @@ -51,8 +51,8 @@ HexDiff::HexDiff(CutterDiff *cutterDiff, QWidget *parent) showAscii(true), showExHex(true), showExAddr(true), - vScrollBar(new AddressRangeScrollBar(this)), - cutterDiff(cutterDiff) + cutterDiff(cutterDiff), + vScrollBar(new AddressRangeScrollBar(this)) { setMouseTracking(true); setFocusPolicy(Qt::FocusPolicy::StrongFocus); @@ -465,7 +465,7 @@ void HexDiff::resizeEvent(QResizeEvent *event) updateViewport(); } -DiffFile HexDiff::getFileFromPos(QPoint &pos) +DiffFile HexDiff::getFileFromPos(QPoint &pos) const { if (ctxA.asciiArea.contains(pos) || ctxA.itemArea.contains(pos)) { return DiffFile::A; @@ -555,7 +555,7 @@ DiffArea HexDiff::posToDiffArea(QPoint &point) const return DiffArea::ItemA; } -bool HexDiff::diffItemsAt(uint64_t addrA) +bool HexDiff::diffItemsAt(uint64_t addrA) const { quint8 a[8]; quint8 b[8]; @@ -564,11 +564,6 @@ bool HexDiff::diffItemsAt(uint64_t addrA) return memcmp(a, b, itemByteLen); } -bool HexDiff::diffByteArrays(QByteArray &a, QByteArray &b) -{ - return a == b; -} - void HexDiff::mousePressEvent(QMouseEvent *event) { QPoint pos(event->pos()); @@ -617,11 +612,6 @@ void HexDiff::wheelEvent(QWheelEvent *event) vScrollBar->showTransientScrollBar(); } -HexDiff::HexNavigationMode HexDiff::defaultNavigationMode() -{ - return HexNavigationMode::Words; -} - bool HexDiff::event(QEvent *event) { // prefer treating keys like 's' 'g' '.' as typing input instead of global shortcuts @@ -640,7 +630,8 @@ bool HexDiff::event(QEvent *event) } void HexDiff::keyPressEvent(QKeyEvent *event) -{ // Navigation mode has to be rechecked +{ + bool select = false; auto moveOrSelect = [event, &select](QKeySequence::StandardKey moveSeq, QKeySequence::StandardKey selectSeq) -> bool { @@ -654,39 +645,34 @@ void HexDiff::keyPressEvent(QKeyEvent *event) return false; }; - if (cursorArea < 2 || navigationMode == HexNavigationMode::Words - || navigationMode == HexNavigationMode::AnyChar) { - if (moveOrSelect(QKeySequence::MoveToNextPage, QKeySequence::SelectNextPage)) { - moveCursor(bytesPerScreen(), select); - } else if (moveOrSelect(QKeySequence::MoveToPreviousPage, - QKeySequence::SelectPreviousPage)) { - moveCursor(-bytesPerScreen(), select); - } else if (moveOrSelect(QKeySequence::MoveToStartOfLine, QKeySequence::SelectStartOfLine)) { - const int linePos = - int((cursor.address % itemRowByteLen()) - (startAddress % itemRowByteLen())); - moveCursor(-linePos, select); - } else if (moveOrSelect(QKeySequence::MoveToEndOfLine, QKeySequence::SelectEndOfLine)) { - const int linePos = - int((cursor.address % itemRowByteLen()) - (startAddress % itemRowByteLen())); - moveCursor(itemRowByteLen() - linePos, select); - } - } - - if (navigationMode == HexNavigationMode::Words || cursorArea < 2) { - if (moveOrSelect(QKeySequence::MoveToNextLine, QKeySequence::SelectNextLine)) { - moveCursor(itemRowByteLen(), select, OverflowMove::Ignore); - } else if (moveOrSelect(QKeySequence::MoveToPreviousLine, - QKeySequence::SelectPreviousLine)) { - moveCursor(-itemRowByteLen(), select, OverflowMove::Ignore); - } else if (moveOrSelect(QKeySequence::MoveToNextChar, QKeySequence::SelectNextChar) - || moveOrSelect(QKeySequence::MoveToNextWord, QKeySequence::SelectNextWord)) { - moveCursor(cursorArea < 2 ? 1 : itemByteLen, select); - } else if (moveOrSelect(QKeySequence::MoveToPreviousChar, QKeySequence::SelectPreviousChar) - || moveOrSelect(QKeySequence::MoveToPreviousWord, - QKeySequence::SelectPreviousWord)) { - moveCursor(cursorArea < 2 ? -1 : -itemByteLen, select); - } - } else if (navigationMode == HexNavigationMode::AnyChar && cursorArea < 1) { + if (moveOrSelect(QKeySequence::MoveToNextPage, QKeySequence::SelectNextPage)) { + moveCursor(bytesPerScreen(), select); + } else if (moveOrSelect(QKeySequence::MoveToPreviousPage, QKeySequence::SelectPreviousPage)) { + moveCursor(-bytesPerScreen(), select); + } else if (moveOrSelect(QKeySequence::MoveToStartOfLine, QKeySequence::SelectStartOfLine)) { + const int linePos = + int((cursor.address % itemRowByteLen()) - (startAddress % itemRowByteLen())); + moveCursor(-linePos, select); + } else if (moveOrSelect(QKeySequence::MoveToEndOfLine, QKeySequence::SelectEndOfLine)) { + const int linePos = + int((cursor.address % itemRowByteLen()) - (startAddress % itemRowByteLen())); + moveCursor(itemRowByteLen() - linePos, select); + } + + if (moveOrSelect(QKeySequence::MoveToNextLine, QKeySequence::SelectNextLine)) { + moveCursor(itemRowByteLen(), select, OverflowMove::Ignore); + } else if (moveOrSelect(QKeySequence::MoveToPreviousLine, QKeySequence::SelectPreviousLine)) { + moveCursor(-itemRowByteLen(), select, OverflowMove::Ignore); + } else if (moveOrSelect(QKeySequence::MoveToNextChar, QKeySequence::SelectNextChar) + || moveOrSelect(QKeySequence::MoveToNextWord, QKeySequence::SelectNextWord)) { + moveCursor(cursorArea < DiffArea::ItemA ? 1 : itemByteLen, select); + } else if (moveOrSelect(QKeySequence::MoveToPreviousChar, QKeySequence::SelectPreviousChar) + || moveOrSelect(QKeySequence::MoveToPreviousWord, + QKeySequence::SelectPreviousWord)) { + moveCursor(cursorArea < DiffArea::ItemA ? -1 : -itemByteLen, select); + } + + if (cursorArea > DiffArea::AsciiB) { if (moveOrSelect(QKeySequence::MoveToNextChar, QKeySequence::SelectNextChar)) { if (select) { moveCursor(itemByteLen, select); @@ -776,7 +762,7 @@ void HexDiff::onHexPairsModeEnabled(bool enable) } void HexDiff::copy() -{ // needs to be changed double cores +{ if (selection.isEmpty() || selection.size() > maxCopySize) { return; } @@ -1278,6 +1264,7 @@ void HexDiff::updateAreasPosition() const qreal yOffset = showHeader ? lineHeight : 0; + // This feels soo reduntant will be fixing once cutter diff is done ctxA.addrArea.setTopLeft(QPointF(0, yOffset)); ctxA.addrArea.setWidth((addrCharLen + (showExAddr ? 2 : 0)) * charWidth); @@ -1454,7 +1441,7 @@ void HexDiff::setCursorOnArea(DiffArea area) QColor HexDiff::itemColor(uint8_t byte) { QColor color(defColor); - + // don't think this is relevant for diffing but keeping it if (byte == 0x00) { color = b0x00Color; } else if (byte == 0x7f) { @@ -1818,12 +1805,18 @@ void HexDiff::shiftStartAddress(int shift) setStartAddress(startAddress + shift); } -void HexDiff::transpose(int transA, int transB) -{ +void HexDiff::transpose(int transA, int transB, bool reset) +{ // Number of bits + if (reset) { + relTranspose = 0; + } + transA /= itemByteLen; + transB /= itemByteLen; relTranspose += transB * itemByteLen - transA * itemByteLen; shiftStartAddress(transA * itemByteLen); clearSelection(); moveCursor(-transB); + updateViewport(); } void HexDiff::updateCursorStatus() diff --git a/src/tools/bindiff/HexDiff.h b/src/tools/bindiff/HexDiff.h index 3c4a865a2a..1dd2d5719e 100644 --- a/src/tools/bindiff/HexDiff.h +++ b/src/tools/bindiff/HexDiff.h @@ -66,21 +66,19 @@ class MemoryDiffData return true; } - bool write(const uint8_t, uint64_t, size_t) {} + static uint64_t maxIndex() { return std::numeric_limits::max(); } - uint64_t maxIndex() { return std::numeric_limits::max(); } - - uint64_t minIndex() { return mFirstBlockAddr; } + uint64_t minIndex() const { return mFirstBlockAddr; } private: - bool orig = true; CutterDiff *cutterDiff; + bool orig = true; QVector mBlocks; uint64_t mFirstBlockAddr = 0; uint64_t mLastValidAddr = 0; }; -// Defining DiffFile Struct for encapsulation +// Defining DiffFile Struct for encapsulation(in some external calls bool is used instead ex: seek) enum DiffFile : ut8 { A, B }; enum DiffArea : ut8 { @@ -91,6 +89,8 @@ enum DiffArea : ut8 { }; // used modulo and comparisons for getting areas for cursorArea and area in mouse press event // both need to be rewritten with a nullarea or no area pointer +// Defining DiffFileContext sharing file specific elements in one unified struct +// I think this shall be moved to the HexDiff class Itself class DiffFileContext { public: @@ -102,6 +102,14 @@ class DiffFileContext QRectF asciiArea; }; +// Kept as it was in HexWidget didn't felt the need to change +// The cursors main address space is that of file A. Any operations +// let it be file reading, data loading, cursor moving it happens relative to +// File A. The Corresponding File A address is translated to File B for File B operations +// Ex: getAddressB, getStartAddressB etc. +// Address specific Data variable only exists for A +// I think we need a virtual address space which will make things more understandable +// may be in future /** * @brief Tracks memory addresses while preventing 64-bit overflow */ @@ -165,7 +173,7 @@ struct HexDiffCursor bool isVisible; bool onAsciiArea; QTimer blinkTimer; - QRectF screenPos; + QRectF screenPos; // since it is a canvas item two rectangles were needed QRectF screenPosB; uint64_t address; QString cachedChar; @@ -179,6 +187,7 @@ struct HexDiffCursor void stopBlinking() { blinkTimer.stop(); } }; +// As it was in HexWidget class HexDiffSelection { public: @@ -278,7 +287,6 @@ class HexDiff : public QScrollArea enum class ColumnMode : ut8 { Fixed, PowerOf2 }; enum class EditWordState : ut8 { Read, WriteNotStarted, WriteNotEdited, WriteEdited }; - enum class HexNavigationMode : ut8 { Words, WordChar, AnyChar }; void setItemSize(int nbytes); void setItemFormat(ItemFormat format); @@ -301,9 +309,11 @@ class HexDiff : public QScrollArea void selectRange(RVA start, RVA end); void clearSelection(); - void transpose(int transA = 0, int transB = 1); + // used to shift itemElements similar to shift in rz-diff + void transpose(int transA = 0, int transB = 1, bool reset = false); void shiftStartAddress(int shift); + // Selection needed address Variables for B since it is accessed by external widgets as well. struct Selection { bool empty; @@ -343,6 +353,10 @@ private slots: private: void updateItemLength(); void updateCounts(); + // DiffFileContext was pased for each of these paint functions. Instead of two calls it has to + // be merged in a single function. ie a single function pains both A's and B's areas Functions + // such as rangePolygons and diffing will be easier and efficient if the paint events are + // coordinated void drawHeader(QPainter &painter, DiffFileContext &ctx); void drawCursor(QPainter &painter, bool shadow = false); void drawAddrArea(QPainter &painter, DiffFileContext &ctx); @@ -354,11 +368,7 @@ private slots: void updateAreasHeight(); enum class OverflowMove : ut8 { Clamp, Ignore }; bool moveCursor(int offset, bool select = false, - OverflowMove overflowMove = - OverflowMove::Clamp); // The entire movecursor has to cursor shall be - // moved in same direction in both do with - // BasicDiffCursor and HexDiffSelection - void moveCursorKeepEditOffset(int byteOffset, bool select, OverflowMove overflowMove); + OverflowMove overflowMove = OverflowMove::Clamp); void setCursorAddr(BasicDiffCursor addr, bool select = false); void updateCursorMeta(); void setCursorOnAscii(bool ascii); @@ -389,12 +399,7 @@ private slots: BasicDiffCursor asciiPosToAddrA(const QPoint &point, bool middle = false) const; BasicDiffCursor currentAreaPosToAddrA(const QPoint &point, bool middle = false) const; BasicDiffCursor mousePosToAddrA(const QPoint &point, bool middle = false) const; - - BasicDiffCursor screenPosToAddrB(const QPoint &point, bool middle = false, - int *wordOffset = nullptr) const; - BasicDiffCursor asciiPosToAddrB(const QPoint &point, bool middle = false) const; - BasicDiffCursor currentAreaPosToAddrB(const QPoint &point, bool middle = false) const; - BasicDiffCursor mousePosToAddrB(const QPoint &point, bool middle = false) const; + // Converts the point to corresponding DiffArea DiffArea posToDiffArea(QPoint &point) const; /** * @brief Rectangle for single item in data area. @@ -407,8 +412,12 @@ private slots: * @param offset relative to first byte on screen * @return */ - QRectF asciiRectangle(int offset, DiffFileContext &ctx); - QVector rangePolygons(RVA start, RVA last, bool ascii, DiffFileContext &ctx); + QRectF + asciiRectangle(int offset, + DiffFileContext &ctx); // selects the ascii rectangles based on the context + QVector + rangePolygons(RVA start, RVA last, bool ascii, + DiffFileContext &ctx); // selects the item rectangles based on the context void updateWidth(); inline qreal itemWidth() const { return itemCharLen * charWidth; } @@ -448,8 +457,6 @@ private slots: bool isFixedWidth() const; - HexNavigationMode defaultNavigationMode(); - void updateViewport(); void scrollLines(int lines, bool clampToScrollBarRange = false); /** @@ -496,7 +503,7 @@ private slots: int itemColumns = 16; ///< Number of columns, single column consists of itemGroupSize items int itemCharLen = 2; int itemPrefixLen = 0; - int relTranspose = 0; ///< relative transpose between the files + int relTranspose = 0; ///< relative transpose between the files(shift) ColumnMode columnMode; ItemFormat itemFormat; @@ -516,8 +523,7 @@ private slots: bool showExHex; bool showExAddr; - bool diffByteArrays(QByteArray &a, QByteArray &b); - bool diffItemsAt(uint64_t addr); + bool diffItemsAt(uint64_t addr) const; QColor borderColor; QColor backgroundColor; @@ -528,7 +534,7 @@ private slots: QColor b0x7fColor; QColor b0xffColor; QColor printableColor; - QColor warningColor; + QColor warningColor; // warning Color is used instead of the actual Diff Color HexdumpRangeDialog rangeDialog; @@ -550,14 +556,13 @@ private slots: DiffFileContext ctxA; DiffFileContext ctxB; - DiffFile getFileFromPos(QPoint &pos); + DiffFile getFileFromPos(QPoint &pos) const; + // Gives selection from particular context HexDiffSelection ctxSelection(DiffFileContext &ctx); + // Gives address at given context uint64_t ctxAddr(uint64_t addrA, DiffFileContext &ctx); - HexNavigationMode navigationMode = HexNavigationMode::Words; - CutterDiff *cutterDiff; - AddressRangeScrollBar *vScrollBar; }; From 1f62551b3443da9f28e1a887eb71048fcb762e88 Mon Sep 17 00:00:00 2001 From: NewtronReal Date: Fri, 3 Jul 2026 20:12:24 +0530 Subject: [PATCH 09/13] lineDiff beginning --- src/CMakeLists.txt | 7 +++-- src/core/CutterDiff.cpp | 12 +++----- src/core/CutterDiff.h | 4 +-- src/tools/bindiff/CutterDiffWindow.cpp | 9 ++++++ src/tools/bindiff/CutterDiffWindow.h | 3 ++ src/tools/bindiff/CutterDiffWindow.ui | 27 +++++++++++++++++- src/tools/bindiff/LineDiffWidget.cpp | 18 ++++++++++++ src/tools/bindiff/LineDiffWidget.h | 24 ++++++++++++++++ src/tools/bindiff/LineDiffWidget.ui | 39 ++++++++++++++++++++++++++ 9 files changed, 129 insertions(+), 14 deletions(-) create mode 100644 src/tools/bindiff/LineDiffWidget.cpp create mode 100644 src/tools/bindiff/LineDiffWidget.h create mode 100644 src/tools/bindiff/LineDiffWidget.ui diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8b0828c2d3..313cc38fe1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -180,6 +180,7 @@ set(SOURCES tools/bindiff/CutterDiffWindow.cpp tools/bindiff/DiffWaitDialog.cpp tools/bindiff/HexDiff.cpp + tools/bindiff/LineDiffWidget.cpp ) set(HEADER_FILES core/Cutter.h @@ -368,6 +369,7 @@ set(HEADER_FILES tools/bindiff/CutterDiffWindow.h tools/bindiff/DiffWaitDialog.h tools/bindiff/HexDiff.h + tools/bindiff/LineDiffWidget.h ) set(UI_FILES dialogs/AboutDialog.ui @@ -455,6 +457,7 @@ set(UI_FILES tools/bindiff/DiffLoadDialog.ui tools/bindiff/CutterDiffWindow.ui tools/bindiff/DiffWaitDialog.ui + tools/bindiff/LineDiffWidget.ui ) set(QRC_FILES resources.qrc @@ -530,9 +533,7 @@ endif() set(CUTTER_SOURCES ${OPTIONS} ${UI_FILES} ${QRC_FILES} ${PLATFORM_RESOURCES} ${SOURCES} ${HEADER_FILES}) source_group(TREE "${CMAKE_CURRENT_LIST_DIR}" FILES ${CUTTER_SOURCES}) -add_executable(Cutter ${CUTTER_SOURCES} ${BINDINGS_SOURCE} - tools/bindiff/CutterDiffWindow.h tools/bindiff/CutterDiffWindow.cpp tools/bindiff/CutterDiffWindow.ui -) +add_executable(Cutter ${CUTTER_SOURCES} ${BINDINGS_SOURCE}) set_target_properties(Cutter PROPERTIES OUTPUT_NAME cutter RUNTIME_OUTPUT_DIRECTORY .. diff --git a/src/core/CutterDiff.cpp b/src/core/CutterDiff.cpp index e2b13b64b0..77116081b6 100644 --- a/src/core/CutterDiff.cpp +++ b/src/core/CutterDiff.cpp @@ -402,18 +402,14 @@ QString CutterDiff::ansiEscapeToHtml(const QString &text) return r; } -QStringList CutterDiff::lineDiff(const char *lines1, const char *lines2) +QString CutterDiff::lineDiff(const char *lines1, const char *lines2) { RzDiff *diff = rz_diff_lines_new(lines1, lines2, nullptr); - const char *results = rz_diff_unified_text(diff, "A", "B", false, true); - QStringList lines = QString::fromUtf8(results).split("\n"); - for (QString &line : lines) { - line = ansiEscapeToHtml(line); - } - return lines; + const char *results = rz_diff_unified_text(diff, "A", "B", false, false); + return QString::fromUtf8(results); } -QStringList CutterDiff::lineDiff(const QString &lines1, const QString &lines2) +QString CutterDiff::lineDiff(const QString &lines1, const QString &lines2) { return lineDiff(lines1.toUtf8().constData(), lines2.toUtf8().constData()); } diff --git a/src/core/CutterDiff.h b/src/core/CutterDiff.h index 7c44101930..328e08d261 100644 --- a/src/core/CutterDiff.h +++ b/src/core/CutterDiff.h @@ -55,8 +55,8 @@ class CUTTER_EXPORT CutterDiff : public QObject QString cmdRawAt(const char *cmd, RVA address, bool orig); QString cmdRaw(const char *cmd, bool orig); RVA getOffset(bool orig); - QStringList lineDiff(const char *lines1, const char *lines2); - QStringList lineDiff(const QString &lines1, const QString &lines2); + QString lineDiff(const char *lines1, const char *lines2); + QString lineDiff(const QString &lines1, const QString &lines2); QString ansiEscapeToHtml(const QString &text); private: diff --git a/src/tools/bindiff/CutterDiffWindow.cpp b/src/tools/bindiff/CutterDiffWindow.cpp index 1096460872..e5bb888698 100644 --- a/src/tools/bindiff/CutterDiffWindow.cpp +++ b/src/tools/bindiff/CutterDiffWindow.cpp @@ -431,6 +431,7 @@ void CutterDiffWindow::onBinDiffCompleted() ui->treeViewFcnsA->setModel(modelA); ui->treeViewFcnsB->setModel(modelB); addHexDiff(); + addLineDiff(); connect(ui->treeViewFcnsA, &CutterTreeView::clicked, this, [this](const QModelIndex &index) { hexDiff->seek(modelA->address(index), true); }); connect(ui->treeViewFcnsB, &CutterTreeView::clicked, this, @@ -493,6 +494,14 @@ void CutterDiffWindow::addHexDiff() hexDiff->setMonospaceFont(font); } +void CutterDiffWindow::addLineDiff() +{ + if (!lineDiff) { + lineDiff = new LineDiffWidget(cutterDiff, this); + } + ui->lineDiffContainer->layout()->addWidget(lineDiff); +} + void CutterDiffWindow::onCopyMD5AClicked() { const QString md5A = ui->bytesMD5A->text(); diff --git a/src/tools/bindiff/CutterDiffWindow.h b/src/tools/bindiff/CutterDiffWindow.h index 37c0da24c7..43abaf0516 100644 --- a/src/tools/bindiff/CutterDiffWindow.h +++ b/src/tools/bindiff/CutterDiffWindow.h @@ -11,6 +11,7 @@ #include #include #include +#include "LineDiffWidget.h" namespace Ui { class CutterDiffWindow; @@ -160,7 +161,9 @@ private slots: QList fcnsA; QList fcnsB; HexDiff *hexDiff = nullptr; + LineDiffWidget *lineDiff = nullptr; void addHexDiff(); + void addLineDiff(); void setupFonts(); void clearParseWindow(); void updateParseWindow(HexDiff::Selection selection); diff --git a/src/tools/bindiff/CutterDiffWindow.ui b/src/tools/bindiff/CutterDiffWindow.ui index 3e324d59cf..30a2c0dc9d 100644 --- a/src/tools/bindiff/CutterDiffWindow.ui +++ b/src/tools/bindiff/CutterDiffWindow.ui @@ -102,7 +102,7 @@ - 0 + 4 @@ -739,6 +739,31 @@ + + + LineDiff + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + diff --git a/src/tools/bindiff/LineDiffWidget.cpp b/src/tools/bindiff/LineDiffWidget.cpp new file mode 100644 index 0000000000..8077dda0d2 --- /dev/null +++ b/src/tools/bindiff/LineDiffWidget.cpp @@ -0,0 +1,18 @@ +#include "LineDiffWidget.h" + +#include "ui_LineDiffWidget.h" + +LineDiffWidget::LineDiffWidget(CutterDiff *cutterDiff,QWidget *parent) : QWidget(parent), ui(new Ui::LineDiffWidget), cutterDiff(cutterDiff) +{ + ui->setupUi(this); + setData(); +} + +LineDiffWidget::~LineDiffWidget() +{ + delete ui; +} + +void LineDiffWidget::setData(){ + ui->textEdit->setPlainText(cutterDiff->lineDiff("hello\nhow\nare\nyou","hellow\nhow\nis\nyou")); +} diff --git a/src/tools/bindiff/LineDiffWidget.h b/src/tools/bindiff/LineDiffWidget.h new file mode 100644 index 0000000000..ccd690089d --- /dev/null +++ b/src/tools/bindiff/LineDiffWidget.h @@ -0,0 +1,24 @@ +#ifndef LINEDIFFWIDGET_H +#define LINEDIFFWIDGET_H + +#include +#include + +namespace Ui { +class LineDiffWidget; +} + +class LineDiffWidget : public QWidget +{ + Q_OBJECT + +public: + explicit LineDiffWidget(CutterDiff *cutterDiff, QWidget *parent = nullptr); + ~LineDiffWidget(); + void setData(); +private: + Ui::LineDiffWidget *ui; + CutterDiff *cutterDiff; +}; + +#endif // LINEDIFFWIDGET_H diff --git a/src/tools/bindiff/LineDiffWidget.ui b/src/tools/bindiff/LineDiffWidget.ui new file mode 100644 index 0000000000..b913b2d1ad --- /dev/null +++ b/src/tools/bindiff/LineDiffWidget.ui @@ -0,0 +1,39 @@ + + + LineDiffWidget + + + + 0 + 0 + 774 + 529 + + + + Form + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + From f5190d4ee94702c4ec239fc53262796246c416d2 Mon Sep 17 00:00:00 2001 From: NewtronReal Date: Fri, 10 Jul 2026 20:51:01 +0530 Subject: [PATCH 10/13] Initial works on Line Diff --- src/core/BinDiff.cpp | 7 +- src/core/BinDiff.h | 7 +- src/core/CutterDiff.cpp | 71 ++++++++++- src/core/CutterDiff.h | 100 +++++++++++++++- src/core/MainWindow.cpp | 6 +- src/core/MainWindow.ui | 6 +- src/tools/bindiff/CutterDiffWindow.cpp | 47 +++++--- src/tools/bindiff/CutterDiffWindow.h | 19 +-- src/tools/bindiff/DiffLoadDialog.cpp | 10 +- src/tools/bindiff/DiffLoadDialog.h | 4 +- src/tools/bindiff/DiffLoadDialog.ui | 4 +- src/tools/bindiff/DiffWaitDialog.cpp | 15 +-- src/tools/bindiff/DiffWaitDialog.h | 6 +- src/tools/bindiff/LineDiffWidget.cpp | 159 +++++++++++++++++++++++-- src/tools/bindiff/LineDiffWidget.h | 49 +++++++- src/tools/bindiff/LineDiffWidget.ui | 2 +- 16 files changed, 434 insertions(+), 78 deletions(-) diff --git a/src/core/BinDiff.cpp b/src/core/BinDiff.cpp index 90dd19ffb9..2a799a8c34 100644 --- a/src/core/BinDiff.cpp +++ b/src/core/BinDiff.cpp @@ -6,9 +6,8 @@ bool BinDiff::threadCallback(const size_t nLeft, const size_t nMatch, void *user return bdiff->updateProgress(nLeft, nMatch); } -BinDiff::BinDiff(CutterDiff *cutterDiff) - : cutterDiff(cutterDiff), - result(nullptr), +BinDiff::BinDiff() + : result(nullptr), continueRun(true), maxTotal(1) #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) @@ -16,6 +15,7 @@ BinDiff::BinDiff(CutterDiff *cutterDiff) mutex(QMutex::Recursive) #endif { + cutterDiff.reset(new CutterDiff()); } BinDiff::~BinDiff() @@ -65,6 +65,7 @@ void BinDiff::run() continueRun = true; maxTotal = 1; // maxTotal must be at least 1. mutex.unlock(); + cutterDiff->initCores(); cutterDiff->openFiles(fileA, fileB); cutterDiff->analyzeCores(level); cutterDiff->syncConfig(); diff --git a/src/core/BinDiff.h b/src/core/BinDiff.h index a501d5d183..29906b7527 100644 --- a/src/core/BinDiff.h +++ b/src/core/BinDiff.h @@ -9,6 +9,8 @@ #include #include + +class CutterDiffWindow; /** * @brief The BinDiff class * Thread run for processing the functional diffing and other large diffing processes. @@ -17,9 +19,10 @@ class BinDiff : public QThread { Q_OBJECT + friend class CutterDiffWindow; public: - explicit BinDiff(CutterDiff *cutterDiff); + explicit BinDiff(); virtual ~BinDiff(); void run(); @@ -41,7 +44,7 @@ public slots: void complete(); private: - CutterDiff *cutterDiff; + std::unique_ptr cutterDiff; RzAnalysisMatchResult *result; bool continueRun; size_t maxTotal; diff --git a/src/core/CutterDiff.cpp b/src/core/CutterDiff.cpp index 77116081b6..14e3d2b3e3 100644 --- a/src/core/CutterDiff.cpp +++ b/src/core/CutterDiff.cpp @@ -83,6 +83,10 @@ bool CutterDiff::openFiles(const QString &fileA, const QString &fileB) qWarning() << tr("cannot set architecture with bits in fileB"); goto fail; } + filePathA = fileA; + filePathB = fileB; + fileNameA = QFileInfo(filePathA).fileName(); + fileNameB = QFileInfo(filePathB).fileName(); syncConfig(); return true; fail: @@ -402,14 +406,73 @@ QString CutterDiff::ansiEscapeToHtml(const QString &text) return r; } -QString CutterDiff::lineDiff(const char *lines1, const char *lines2) +QString CutterDiff::disassembleFunction(RVA addr, bool orig) +{ + LOCK(); + RzCore *core = orig ? coreA : coreB; + RzAnalysisFunction *function = rz_analysis_get_fcn_in( + core->analysis, addr, RZ_ANALYSIS_FCN_TYPE_FCN | RZ_ANALYSIS_FCN_TYPE_SYM); + if (!function) { + qWarning() << QString("Could not load function at %1").arg(QString::number(addr, 16)); + return {}; + } + auto vec = fromOwned( + rz_pvector_new(reinterpret_cast(rz_analysis_disasm_text_free))); + if (!vec) { + return {}; + } + const uint64_t start = function->addr; + const uint64_t end = rz_analysis_function_max_addr(function); + if (start > end) { + qWarning() << "Start address is greater than end address of the function"; + return {}; + } + const uint64_t size = end - start; + QByteArray array; + array.resize(size); + rz_io_read_at_mapped(core->io, start, reinterpret_cast(array.data()), size); + RzCoreDisasmOptions disasmOptions = { .cbytes = 1, .function = function, .vec = vec.get() }; + TempDiffConfig config(this, orig); + config.setConfigi("scr.utf8", 0); + config.setConfigi("asm.offset", 0); + config.setConfigi("asm.lines", 0); + config.setConfigi("asm.cmt.right", 0); + config.setConfigi("asm.lines.fcn", 0); + config.setConfigi("asm.bytes", 0); + config.setConfigi("asm.comments", 0); + config.setConfigi("scr.color", COLOR_MODE_DISABLED); + rz_core_print_disasm(core, start, reinterpret_cast(array.data()), size, size, nullptr, + &disasmOptions); + QString r; + for (const auto &t : CutterPVector(vec.get())) { + const QString text = t->text; + r.append(text); + r.append("\n"); + } + return r; +} + +RzDiff *CutterDiff::diffFunctionDissas(RVA addrA, RVA addrB) +{ + LOCK(); + const QString disasA = disassembleFunction(addrA, true); + const QString disasB = disassembleFunction(addrB, false); + return lineDiff(disasA, disasB); +} + +RzDiff *CutterDiff::lineDiff(const char *lines1, const char *lines2) { RzDiff *diff = rz_diff_lines_new(lines1, lines2, nullptr); - const char *results = rz_diff_unified_text(diff, "A", "B", false, false); - return QString::fromUtf8(results); + return diff; } -QString CutterDiff::lineDiff(const QString &lines1, const QString &lines2) +RzDiff *CutterDiff::lineDiff(const QString &lines1, const QString &lines2) { return lineDiff(lines1.toUtf8().constData(), lines2.toUtf8().constData()); } + +CutterRzList*/> CutterDiff::lineDiffOpsGrouped(RzDiff *diff) const +{ + auto *groups = rz_diff_unified_text_grouped(diff); + return CutterRzList*/>(groups); +} diff --git a/src/core/CutterDiff.h b/src/core/CutterDiff.h index 328e08d261..08ba53583f 100644 --- a/src/core/CutterDiff.h +++ b/src/core/CutterDiff.h @@ -55,19 +55,30 @@ class CUTTER_EXPORT CutterDiff : public QObject QString cmdRawAt(const char *cmd, RVA address, bool orig); QString cmdRaw(const char *cmd, bool orig); RVA getOffset(bool orig); - QString lineDiff(const char *lines1, const char *lines2); - QString lineDiff(const QString &lines1, const QString &lines2); + RzDiff *lineDiff(const char *lines1, const char *lines2); // Own RzDiff + RzDiff *lineDiff(const QString &lines1, const QString &lines2); // Own RzDiff + RzDiff *diffFunctionDissas(RVA addrA, RVA addrB); // Own RzDiff + QString diffFunctionDecomp(RVA addrA, RVA addrB); + QString diffFunctionRzIL(RVA addrA, RVA addrB); + QString disassembleFunction(RVA addr, bool orig); QString ansiEscapeToHtml(const QString &text); + CutterRzList*/> lineDiffOpsGrouped(RzDiff *diff) const; + QString getFileName(bool orig = true) const { return orig ? fileNameA : fileNameB; } + QString getFilePath(bool orig = true) const { return orig ? filePathA : filePathB; } private: RzCore *coreA = nullptr; RzCore *coreB = nullptr; + QString fileNameA; + QString fileNameB; + QString filePathA; + QString filePathB; #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) QMutex mutex; #else QRecursiveMutex mutex; #endif - RzList *getFunctions(RzAnalysis *analysis, int compareLogic); + RzList *getFunctions(RzAnalysis *analysis, int compareLogic); // Own RzList signals: }; @@ -89,4 +100,87 @@ class CutterDiffLocked CutterDiff *operator->() && = delete; }; +class TempDiffConfig +{ +public: + explicit TempDiffConfig(CutterDiff *cutterDiff, bool orig = true) + : cutterDiff(cutterDiff), orig(orig) + { + } + + ~TempDiffConfig() + { + const CutterDiffLocked lock(cutterDiff); + const RzCore *const core = orig ? lock.coreA : lock.coreB; + + for (auto it = iHash.cbegin(); it != iHash.cend(); ++it) { + const QByteArray key = it.key().toUtf8(); + rz_config_set_i(core->config, key.constData(), it.value()); + } + + for (auto it = bHash.cbegin(); it != bHash.cend(); ++it) { + const QByteArray key = it.key().toUtf8(); + rz_config_set_b(core->config, key.constData(), it.value()); + } + + for (auto it = cHash.cbegin(); it != cHash.cend(); ++it) { + const QByteArray key = it.key().toUtf8(); + const QByteArray value = it.value().toUtf8(); + rz_config_set(core->config, key.constData(), value.constData()); + } + } + + void setConfigi(const QString &config, ut64 val) + { + const CutterDiffLocked lock(cutterDiff); + const RzCore *const core = orig ? lock.coreA : lock.coreB; + + const QByteArray key = config.toUtf8(); + + if (!iHash.contains(config)) { + iHash.insert(config, rz_config_get_i(core->config, key.constData())); + } + + rz_config_set_i(core->config, key.constData(), val); + } + + void setConfigb(const QString &config, bool val) + { + const CutterDiffLocked lock(cutterDiff); + const RzCore *const core = orig ? lock.coreA : lock.coreB; + + const QByteArray key = config.toUtf8(); + + if (!bHash.contains(config)) { + bHash.insert(config, rz_config_get_b(core->config, key.constData())); + } + + rz_config_set_b(core->config, key.constData(), val); + } + + void setConfig(const QString &config, const QString &val) + { + const CutterDiffLocked lock(cutterDiff); + const RzCore *const core = orig ? lock.coreA : lock.coreB; + + const QByteArray key = config.toUtf8(); + + if (!cHash.contains(config)) { + const char *oldValue = rz_config_get(core->config, key.constData()); + cHash.insert(config, oldValue ? QString::fromUtf8(oldValue) : QString()); + } + + const QByteArray value = val.toUtf8(); + rz_config_set(core->config, key.constData(), value.constData()); + } + +private: + CutterDiff *cutterDiff; + const bool orig; + + QHash iHash; + QHash bHash; + QHash cHash; +}; + #endif // CUTTERDIFF_H diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp index 2974d8d010..c6f7a51469 100644 --- a/src/core/MainWindow.cpp +++ b/src/core/MainWindow.cpp @@ -115,7 +115,7 @@ // Tools #include "tools/basefind/BaseFindDialog.h" -#include "tools/bindiff/CutterDiffWindow.h" +#include "tools/bindiff/DiffLoadDialog.h" template T *getNewInstance(MainWindow *m) @@ -1752,8 +1752,8 @@ void MainWindow::onActionRefreshPanelsTriggered() void MainWindow::onActionDiffFilesTriggered() { - auto cutterDiffWindow = new CutterDiffWindow(); - cutterDiffWindow->show(); + auto *diffLoad = new DiffLoadDialog(this); + diffLoad->show(); } void MainWindow::onActionAnalyzeTriggered() const diff --git a/src/core/MainWindow.ui b/src/core/MainWindow.ui index 8af4123993..6ab457ceac 100644 --- a/src/core/MainWindow.ui +++ b/src/core/MainWindow.ui @@ -51,10 +51,10 @@ - 337 + 340 86 206 - 414 + 389 @@ -91,7 +91,6 @@ - @@ -134,6 +133,7 @@ &Tools + diff --git a/src/tools/bindiff/CutterDiffWindow.cpp b/src/tools/bindiff/CutterDiffWindow.cpp index e5bb888698..2fad3f2b9e 100644 --- a/src/tools/bindiff/CutterDiffWindow.cpp +++ b/src/tools/bindiff/CutterDiffWindow.cpp @@ -12,6 +12,9 @@ FunctionListModel::FunctionListModel(QList *list, QObject * : AddressableItemModel<>(parent), list(list) { } + +FunctionListModel::~FunctionListModel() {} + QModelIndex FunctionListModel::index(int row, int column, const QModelIndex &parent) const { if (parent.isValid()) { @@ -118,6 +121,8 @@ DiffMatchModel::DiffMatchModel(QList *list, QColor cPer { } +DiffMatchModel::~DiffMatchModel() {} + int DiffMatchModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { @@ -241,6 +246,8 @@ DiffMismatchModel::DiffMismatchModel(QList *list, QObject * { } +DiffMismatchModel::~DiffMismatchModel() {} + int DiffMismatchModel::rowCount(const QModelIndex &) const { return list->count(); @@ -322,26 +329,27 @@ QVariant DiffMismatchModel::headerData(int section, Qt::Orientation, int role) c } } -CutterDiffWindow::CutterDiffWindow(QWidget *parent) +CutterDiffWindow::CutterDiffWindow(std::unique_ptr bDiff, QWidget *parent) : QMainWindow(parent), ui(new Ui::CutterDiffWindow), - cutterDiff(new CutterDiff), - bDiff(new BinDiff(cutterDiff)) + bDiff(std::move(bDiff)), + cutterDiff(bDiff->cutterDiff.get()) { ui->setupUi(this); - cutterDiff->initCores(); ui->splitter->setSizes({ 250, 750 }); ui->splitterHexView->setSizes({ 750, 250 }); ui->treeViewMatches->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->treeViewMatches, &CutterTreeView::customContextMenuRequested, this, &CutterDiffWindow::showContextMenuMatches); connect(ui->treeViewMatches, &CutterTreeView::clicked, this, &CutterDiffWindow::selectFunction); - connect(bDiff, &BinDiff::complete, this, &CutterDiffWindow::onBinDiffCompleted); - connect(ui->actionDiffNewFiles, &QAction::triggered, this, - &CutterDiffWindow::onActionDiffNewFile); + // connect(bDiff, &BinDiff::complete, this, &CutterDiffWindow::onBinDiffCompleted); + // connect(ui->actionDiffNewFiles, &QAction::triggered, this, + // &CutterDiffWindow::onActionDiffNewFile); ui->tabParsing->hide(); setupFonts(); showMaximized(); + addLineDiff(); + showDiff(); } CutterDiffWindow::~CutterDiffWindow() @@ -382,7 +390,8 @@ void CutterDiffWindow::showContextMenuMatches(const QPoint &pos) ui->tabWidget->setCurrentIndex(3); hexDiff->seek(addr.first); } else if (selected == diffFunctionLines) { - // once diffLineView is implemented + lineDiff->fetchFunctionDisas(addr.first, addr.second); + ui->tabWidget->setCurrentIndex(4); } else if (selected == copyAddress) { if (index.column() < DiffMatchModel::AddressMod) { QApplication::clipboard()->setText(QString::number(addr.first)); @@ -394,13 +403,14 @@ void CutterDiffWindow::showContextMenuMatches(const QPoint &pos) void CutterDiffWindow::setupFonts() {} -void CutterDiffWindow::onBinDiffCompleted() +void CutterDiffWindow::showDiff() { if (!bDiff->hasData()) { return; } - const QColor perfect = Config()->getColor("gui.match.perfect"); + const QColor perfect = Config()->getColor( + "gui.match.perfect"); // needs to be added to either cutter or in rizin const QColor partial = Config()->getColor("gui.match.partial"); listMatch = bDiff->matches(); @@ -408,8 +418,8 @@ void CutterDiffWindow::onBinDiffCompleted() listAdd = bDiff->mismatch(false); matches = new DiffMatchModel(&listMatch, perfect, partial, this); - added = new DiffMismatchModel(&listDel, this); - removed = new DiffMismatchModel(&listAdd, this); + added = new DiffMismatchModel(&listAdd, this); + removed = new DiffMismatchModel(&listDel, this); ui->treeViewMatches->setModel(matches); ui->treeViewMatches->sortByColumn(DiffMatchModel::Similarity, Qt::AscendingOrder); @@ -429,9 +439,10 @@ void CutterDiffWindow::onBinDiffCompleted() modelB = new FunctionListModel(&fcnsB, this); ui->treeViewFcnsA->setModel(modelA); + ui->fcnsALabel->setText(cutterDiff->getFileName(true)); ui->treeViewFcnsB->setModel(modelB); + ui->fcnsBLabel->setText(cutterDiff->getFileName(false)); addHexDiff(); - addLineDiff(); connect(ui->treeViewFcnsA, &CutterTreeView::clicked, this, [this](const QModelIndex &index) { hexDiff->seek(modelA->address(index), true); }); connect(ui->treeViewFcnsB, &CutterTreeView::clicked, this, @@ -478,11 +489,11 @@ void CutterDiffWindow::onBinDiffCompleted() connect(hexDiff, &HexDiff::selectionChanged, this, &CutterDiffWindow::selectionChanged); } -void CutterDiffWindow::onActionDiffNewFile() -{ - auto loadDiff = new DiffLoadDialog(bDiff, this); - loadDiff->show(); -} +// void CutterDiffWindow::onActionDiffNewFile() +// { +// auto loadDiff = new DiffLoadDialog(bDiff, this); +// loadDiff->show(); +// } void CutterDiffWindow::addHexDiff() { diff --git a/src/tools/bindiff/CutterDiffWindow.h b/src/tools/bindiff/CutterDiffWindow.h index 43abaf0516..de23c93cd1 100644 --- a/src/tools/bindiff/CutterDiffWindow.h +++ b/src/tools/bindiff/CutterDiffWindow.h @@ -2,6 +2,7 @@ #define CUTTERDIFFWINDOW_H #include "HexDiff.h" +#include "LineDiffWidget.h" #include #include @@ -11,7 +12,6 @@ #include #include #include -#include "LineDiffWidget.h" namespace Ui { class CutterDiffWindow; @@ -39,6 +39,7 @@ class DiffMatchModel : public QAbstractListModel DiffMatchModel(QList *list, QColor cPerf, QColor cPart, QObject *parent = nullptr); + ~DiffMatchModel(); QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; @@ -50,6 +51,9 @@ class DiffMatchModel : public QAbstractListModel QPair address(const QModelIndex &index) const; + void diffNew(const QString &fileA, const QString &fileB, int compareLogic, int analysisLevel); + void clearDiff(); + private: QList *list; @@ -77,6 +81,7 @@ class DiffMismatchModel : public QAbstractListModel }; DiffMismatchModel(QList *list, QObject *parent = nullptr); + ~DiffMismatchModel(); QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; @@ -93,7 +98,7 @@ class FunctionListModel : public AddressableItemModel<> Q_OBJECT public: FunctionListModel(QList *list, QObject *parent = nullptr); - ~FunctionListModel() {} + ~FunctionListModel(); enum Column : ut8 { Name, Offset, ColumnCount }; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; @@ -118,12 +123,10 @@ class CutterDiffWindow : public QMainWindow Q_OBJECT public: - explicit CutterDiffWindow(QWidget *parent = nullptr); + explicit CutterDiffWindow(std::unique_ptr bDiff, QWidget *parent = nullptr); ~CutterDiffWindow(); - public slots: - void onBinDiffCompleted(); - void onActionDiffNewFile(); + // void onBinDiffCompleted(); private slots: void onCopyMD5AClicked(); void onCopyShA1AClicked(); @@ -136,12 +139,13 @@ private slots: void selectionChanged(HexDiff::Selection selection); void showContextMenuMatches(const QPoint &pos); void selectFunction(const QModelIndex &index); + void showDiff(); private: Ui::CutterDiffWindow *ui; CutterDiff *cutterDiff; // BinDiff thread which fetches basic analysis results and processing - BinDiff *bDiff; + std::unique_ptr bDiff; // model for matched functions of both binaries DiffMatchModel *matches; // model for added functions @@ -167,7 +171,6 @@ private slots: void setupFonts(); void clearParseWindow(); void updateParseWindow(HexDiff::Selection selection); - QSyntaxHighlighter *syntaxHighLighter; }; #endif // CUTTERDIFFWINDOW_H diff --git a/src/tools/bindiff/DiffLoadDialog.cpp b/src/tools/bindiff/DiffLoadDialog.cpp index c67c3ef6fb..a1a2bffe05 100644 --- a/src/tools/bindiff/DiffLoadDialog.cpp +++ b/src/tools/bindiff/DiffLoadDialog.cpp @@ -10,8 +10,7 @@ #include #include -DiffLoadDialog::DiffLoadDialog(BinDiff *bDiff, QWidget *parent) - : QDialog(parent), ui(new Ui::DiffLoadDialog), bDiff(bDiff) +DiffLoadDialog::DiffLoadDialog(QWidget *parent) : QDialog(parent), ui(new Ui::DiffLoadDialog) { ui->setupUi(this); setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); @@ -87,7 +86,7 @@ void DiffLoadDialog::onButtonFileAOpenClicked() return; } - ui->lineEditFileB->setText(fileName); + ui->lineEditFileA->setText(fileName); } void DiffLoadDialog::onButtonFileBOpenClicked() @@ -113,7 +112,7 @@ void DiffLoadDialog::onButtonFileBOpenClicked() return; } - ui->lineEditFileA->setText(fileName); + ui->lineEditFileB->setText(fileName); } void DiffLoadDialog::onButtonBoxAccepted() @@ -126,10 +125,9 @@ void DiffLoadDialog::onButtonBoxAccepted() QMessageBox::warning(this, tr("Empty FileB"), tr("Select a file for diffing.")); return; } - auto waitDialog = new DiffWaitDialog(bDiff, this); + auto waitDialog = new DiffWaitDialog(parentWidget()); waitDialog->show(ui->lineEditFileA->text(), ui->lineEditFileB->text(), ui->comboBoxAnalysis->currentIndex(), ui->comboBoxCompare->currentIndex()); - printf("hello world"); emit startDiffing(); } diff --git a/src/tools/bindiff/DiffLoadDialog.h b/src/tools/bindiff/DiffLoadDialog.h index a9fd1091fb..f373af5fbc 100644 --- a/src/tools/bindiff/DiffLoadDialog.h +++ b/src/tools/bindiff/DiffLoadDialog.h @@ -6,7 +6,6 @@ #include #include -#include #include #include @@ -19,7 +18,7 @@ class DiffLoadDialog : public QDialog Q_OBJECT public: - explicit DiffLoadDialog(BinDiff *bDiff, QWidget *parent = nullptr); + explicit DiffLoadDialog(QWidget *parent = nullptr); ~DiffLoadDialog(); QString getFileA() const; @@ -38,7 +37,6 @@ private slots: private: std::unique_ptr ui; - BinDiff *bDiff; }; #endif // DIFF_LOAD_DIALOG_H diff --git a/src/tools/bindiff/DiffLoadDialog.ui b/src/tools/bindiff/DiffLoadDialog.ui index f5b4563260..e1b6e4e9e2 100644 --- a/src/tools/bindiff/DiffLoadDialog.ui +++ b/src/tools/bindiff/DiffLoadDialog.ui @@ -32,7 +32,7 @@ QLayout::SizeConstraint::SetMinimumSize - + @@ -52,7 +52,7 @@ - + diff --git a/src/tools/bindiff/DiffWaitDialog.cpp b/src/tools/bindiff/DiffWaitDialog.cpp index 0ba21aea0d..0a99f2153c 100644 --- a/src/tools/bindiff/DiffWaitDialog.cpp +++ b/src/tools/bindiff/DiffWaitDialog.cpp @@ -7,12 +7,13 @@ #include #include -DiffWaitDialog::DiffWaitDialog(BinDiff *bDiff, QWidget *parent) - : QDialog(parent), bDiff(bDiff), timer(parent), ui(new Ui::DiffWaitDialog) +DiffWaitDialog::DiffWaitDialog(QWidget *parent) + : QDialog(parent), timer(parent), ui(new Ui::DiffWaitDialog) { ui->setupUi(this); setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); setModal(true); + bDiff.reset(new BinDiff()); ui->lineEditNFuncs->setReadOnly(true); ui->lineEditMatches->setReadOnly(true); @@ -33,14 +34,13 @@ DiffWaitDialog::~DiffWaitDialog() bDiff->cancel(); bDiff->wait(); } - delete bDiff; } void DiffWaitDialog::show(const QString &original, const QString &modified, int level, int compare) { - connect(this, &DiffWaitDialog::cancelJob, bDiff, &BinDiff::cancel); - connect(bDiff, &BinDiff::progress, this, &DiffWaitDialog::onProgress); - connect(bDiff, &BinDiff::complete, this, &DiffWaitDialog::onCompletion); + connect(this, &DiffWaitDialog::cancelJob, bDiff.get(), &BinDiff::cancel); + connect(bDiff.get(), &BinDiff::progress, this, &DiffWaitDialog::onProgress); + connect(bDiff.get(), &BinDiff::complete, this, &DiffWaitDialog::onCompletion); connect(&timer, &QTimer::timeout, this, &DiffWaitDialog::updateElapsedTime); ui->lineEditOriginal->setText(original); @@ -79,7 +79,8 @@ void DiffWaitDialog::onProgress(BinDiffStatusDescription status) void DiffWaitDialog::onCompletion() { timer.stop(); - + auto *diffWindow = new CutterDiffWindow(std::move(bDiff), parentWidget()); + diffWindow->show(); close(); } diff --git a/src/tools/bindiff/DiffWaitDialog.h b/src/tools/bindiff/DiffWaitDialog.h index e99d9f43be..71115e7f9c 100644 --- a/src/tools/bindiff/DiffWaitDialog.h +++ b/src/tools/bindiff/DiffWaitDialog.h @@ -1,6 +1,8 @@ #ifndef DIFF_WAIT_DIALOG_H #define DIFF_WAIT_DIALOG_H +#include "CutterDiffWindow.h" + #include #include #include @@ -20,7 +22,7 @@ class DiffWaitDialog : public QDialog Q_OBJECT public: - explicit DiffWaitDialog(BinDiff *bDiff, QWidget *parent = nullptr); + explicit DiffWaitDialog(QWidget *parent = nullptr); ~DiffWaitDialog(); void show(const QString &original, const QString &modified, int level, int compare); @@ -38,7 +40,7 @@ private slots: private: QElapsedTimer eTimer; - BinDiff *bDiff; + std::unique_ptr bDiff; QTimer timer; std::unique_ptr ui; }; diff --git a/src/tools/bindiff/LineDiffWidget.cpp b/src/tools/bindiff/LineDiffWidget.cpp index 8077dda0d2..14b9a36423 100644 --- a/src/tools/bindiff/LineDiffWidget.cpp +++ b/src/tools/bindiff/LineDiffWidget.cpp @@ -1,18 +1,161 @@ #include "LineDiffWidget.h" -#include "ui_LineDiffWidget.h" +#include +#include +#include +#include +#include -LineDiffWidget::LineDiffWidget(CutterDiff *cutterDiff,QWidget *parent) : QWidget(parent), ui(new Ui::LineDiffWidget), cutterDiff(cutterDiff) +LineDiffWidget::LineDiffWidget(CutterDiff *cutterDiff, QWidget *parent) + : QWidget(parent), + cutterDiff(cutterDiff), + leftEdit(new DiffTextEdit(this)), + rightEdit(new DiffTextEdit(this)) { - ui->setupUi(this); - setData(); + auto *layoutV = new QVBoxLayout(this); + layoutV->setContentsMargins(0, 0, 0, 0); + layoutV->setSpacing(0); + + auto *splitter = new QSplitter(Qt::Horizontal, this); + splitter->addWidget(leftEdit); + splitter->addWidget(rightEdit); + + layoutV->addWidget(splitter); } -LineDiffWidget::~LineDiffWidget() +LineDiffWidget::~LineDiffWidget() {} + +void LineDiffWidget::fetchFunctionDisas(RVA addrA, RVA addrB) { - delete ui; + char *stringUtf; + RzDiff *diffedLines = cutterDiff->diffFunctionDissas(addrA, addrB); + const CutterRzList*/> groups = + cutterDiff->lineDiffOpsGrouped(diffedLines); + for (const RzList /**/ *group : groups) { + for (RzDiffOp *op : CutterRzList(group)) { + switch (op->type) { + case RZ_DIFF_OP_EQUAL: { + stringUtf = rz_diff_op_stringify(diffedLines, op, true); + const QString opString = QString::fromUtf8(stringUtf); + leftEdit->insertPlainText(opString); + rightEdit->insertPlainText(opString); + break; + } + case RZ_DIFF_OP_DELETE: { + stringUtf = rz_diff_op_stringify(diffedLines, op, true); + leftEdit->insertFormatted(QString::fromUtf8(stringUtf), QColor(255, 0, 0, 100)); + break; + } + case RZ_DIFF_OP_INSERT: { + stringUtf = rz_diff_op_stringify(diffedLines, op, false); + rightEdit->insertFormatted(QString::fromUtf8(stringUtf), QColor(0, 255, 0, 100)); + break; + } + case RZ_DIFF_OP_REPLACE: { + stringUtf = rz_diff_op_stringify(diffedLines, op, true); + leftEdit->insertFormatted(QString::fromUtf8(stringUtf), QColor(255, 0, 0, 100)); + stringUtf = rz_diff_op_stringify(diffedLines, op, false); + rightEdit->insertFormatted(QString::fromUtf8(stringUtf), QColor(0, 255, 0, 100)); + break; + } + default: + break; + } + } + } + rz_diff_free(diffedLines); } -void LineDiffWidget::setData(){ - ui->textEdit->setPlainText(cutterDiff->lineDiff("hello\nhow\nare\nyou","hellow\nhow\nis\nyou")); +DiffTextEdit::DiffTextEdit(QWidget *parent) : QPlainTextEdit(parent) +{ + setReadOnly(true); + lineNumberArea = new LineNumberArea(this); + connect(this, &QPlainTextEdit::blockCountChanged, this, &DiffTextEdit::updateLineNumberArea); + + connect(verticalScrollBar(), &QScrollBar::valueChanged, this, + &DiffTextEdit::updateLineNumberArea); + + connect(this, &QPlainTextEdit::cursorPositionChanged, this, + &DiffTextEdit::highlightCurrentLine); } + +DiffTextEdit::~DiffTextEdit() {} + +void DiffTextEdit::highlightCurrentLine() +{ + QList selections; + + QTextEdit::ExtraSelection selection; + + QColor lineColor = QColor(60, 60, 60); // Choose a theme-appropriate color + selection.format.setBackground(lineColor); + selection.format.setProperty(QTextFormat::FullWidthSelection, true); + + selection.cursor = textCursor(); + selection.cursor.clearSelection(); + + selections.append(selection); + + setExtraSelections(selections); +} + +void DiffTextEdit::resizeEvent(QResizeEvent *event) +{ + QPlainTextEdit::resizeEvent(event); + + setViewportMargins(lineNumberAreaWidth(), 0, 0, 0); + + lineNumberArea->setGeometry(0, 0, lineNumberAreaWidth(), height()); +} + +int DiffTextEdit::lineNumberAreaWidth() const +{ + const int digits = QString::number(document()->blockCount()).length(); + + return 8 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits; +} + +void DiffTextEdit::updateLineNumberArea() +{ + setViewportMargins(lineNumberAreaWidth(), 0, 0, 0); + lineNumberArea->update(); +} + +void DiffTextEdit::lineNumberAreaPaintEvent(QPaintEvent *event) +{ + QPainter painter(lineNumberArea); + + painter.fillRect(event->rect(), QColor(240, 240, 240)); + + QTextBlock block = firstVisibleBlock(); + + while (block.isValid()) { + const int top = qRound(blockBoundingGeometry(block).translated(contentOffset()).top()); + const int bottom = top + qRound(blockBoundingRect(block).height()); + ; + + if (bottom >= event->rect().top() && top <= event->rect().bottom()) { + painter.setPen(Qt::darkGray); + + painter.drawText(0, top, lineNumberArea->width() - 4, fontMetrics().height(), + Qt::AlignRight, QString::number(block.blockNumber() + 1)); + } + block = block.next(); + } +} + +void DiffTextEdit::insertFormatted(const QString &text, const QColor &color) +{ + QTextCursor cursor = textCursor(); + cursor.movePosition(QTextCursor::End); + + const QTextCharFormat oldFormat = cursor.charFormat(); + + QTextCharFormat fmt = oldFormat; + fmt.setBackground(color); + + cursor.insertText(text, fmt); + + cursor.setCharFormat(oldFormat); + setTextCursor(cursor); +} \ No newline at end of file diff --git a/src/tools/bindiff/LineDiffWidget.h b/src/tools/bindiff/LineDiffWidget.h index ccd690089d..8f7bad8a6f 100644 --- a/src/tools/bindiff/LineDiffWidget.h +++ b/src/tools/bindiff/LineDiffWidget.h @@ -1,12 +1,13 @@ #ifndef LINEDIFFWIDGET_H #define LINEDIFFWIDGET_H +#include #include + #include -namespace Ui { -class LineDiffWidget; -} +class DiffTextEdit; +class LineNumberArea; class LineDiffWidget : public QWidget { @@ -15,10 +16,48 @@ class LineDiffWidget : public QWidget public: explicit LineDiffWidget(CutterDiff *cutterDiff, QWidget *parent = nullptr); ~LineDiffWidget(); - void setData(); + void fetchFunctionDisas(RVA addrA, RVA addrB); + +protected: private: - Ui::LineDiffWidget *ui; CutterDiff *cutterDiff; + DiffTextEdit *leftEdit; + DiffTextEdit *rightEdit; +}; + +class DiffTextEdit : public QPlainTextEdit +{ + Q_OBJECT + +public: + explicit DiffTextEdit(QWidget *parent = nullptr); + ~DiffTextEdit(); + int lineNumberAreaWidth() const; + void lineNumberAreaPaintEvent(QPaintEvent *event); + void insertFormatted(const QString &text, const QColor &color); + +protected: + void resizeEvent(QResizeEvent *event) override; +private slots: + void updateLineNumberArea(); + +private: + LineNumberArea *lineNumberArea; + void highlightCurrentLine(); +}; + +class LineNumberArea : public QWidget +{ +public: + explicit LineNumberArea(DiffTextEdit *editor) : QWidget(editor), editor(editor) {} + + QSize sizeHint() const override { return QSize(editor->lineNumberAreaWidth(), 0); } + +protected: + void paintEvent(QPaintEvent *event) override { editor->lineNumberAreaPaintEvent(event); } + +private: + DiffTextEdit *editor; }; #endif // LINEDIFFWIDGET_H diff --git a/src/tools/bindiff/LineDiffWidget.ui b/src/tools/bindiff/LineDiffWidget.ui index b913b2d1ad..a429bc7647 100644 --- a/src/tools/bindiff/LineDiffWidget.ui +++ b/src/tools/bindiff/LineDiffWidget.ui @@ -30,7 +30,7 @@ 0 - + From ebd2fbaf64e9827e0bd78cd26ef04d530f2ee126 Mon Sep 17 00:00:00 2001 From: NewtronReal Date: Sun, 12 Jul 2026 23:35:25 +0530 Subject: [PATCH 11/13] Color support and CutterDiff changes for Cores and console --- src/core/BinDiff.cpp | 69 +++++++++----- src/core/BinDiff.h | 4 + src/core/CutterDiff.cpp | 11 ++- src/tools/bindiff/CutterDiffWindow.cpp | 125 +++++++++++++++++++------ src/tools/bindiff/CutterDiffWindow.h | 18 ++-- src/tools/bindiff/CutterDiffWindow.ui | 42 +++++++-- src/tools/bindiff/DiffLoadDialog.cpp | 60 +++++++++--- src/tools/bindiff/DiffLoadDialog.h | 2 + src/tools/bindiff/DiffLoadDialog.ui | 84 ++++++++++------- src/tools/bindiff/DiffWaitDialog.cpp | 3 +- src/tools/bindiff/DiffWaitDialog.ui | 12 +-- src/tools/bindiff/HexDiff.cpp | 12 +-- src/tools/bindiff/HexDiff.h | 1 - 13 files changed, 316 insertions(+), 127 deletions(-) diff --git a/src/core/BinDiff.cpp b/src/core/BinDiff.cpp index 2a799a8c34..11f11daf6e 100644 --- a/src/core/BinDiff.cpp +++ b/src/core/BinDiff.cpp @@ -66,11 +66,11 @@ void BinDiff::run() maxTotal = 1; // maxTotal must be at least 1. mutex.unlock(); cutterDiff->initCores(); + cutterDiff->syncConfig(); cutterDiff->openFiles(fileA, fileB); cutterDiff->analyzeCores(level); - cutterDiff->syncConfig(); result = cutterDiff->matchFunctions(compareLogic, threadCallback, this); - + sortFunctions(); mutex.lock(); const bool canComplete = continueRun; mutex.unlock(); @@ -99,52 +99,71 @@ static void setFunctionDescription(FunctionDescription *desc, const RzAnalysisFu desc->stackframe = func->maxstack; } -QList BinDiff::matches() +void BinDiff::sortFunctions() { - QList pairs; + if (!result) { + return; + } + // Get similar pairs injectively const RzAnalysisMatchPair *pair = nullptr; const RzListIter *it = nullptr; const RzAnalysisFunction *fcnA = nullptr; const RzAnalysisFunction *fcnB = nullptr; - if (!result) { - return pairs; - } + QHash matchedHash; CutterRzListForeach (result->matches, it, RzAnalysisMatchPair, pair) { BinDiffMatchDescription desc; fcnA = static_cast(pair->pair_a); fcnB = static_cast(pair->pair_b); + auto it = matchedHash.find(fcnB); + if (it == matchedHash.end()) { + setFunctionDescription(&desc.original, fcnA); + setFunctionDescription(&desc.modified, fcnB); + + desc.simtype = RZ_ANALYSIS_SIMILARITY_TYPE_STR(pair->similarity); + desc.similarity = pair->similarity; + + matchedList.push_back(desc); + matchedHash[fcnB] = matchedList.size() - 1; + if (removedSet.contains(fcnA)) { + removedSet.remove(fcnA); + } + } else { + if (matchedList[it.value()].similarity < pair->similarity) { + setFunctionDescription(&matchedList[it.value()].original, fcnA); + matchedList[it.value()].simtype = RZ_ANALYSIS_SIMILARITY_TYPE_STR(pair->similarity); + matchedList[it.value()].similarity = pair->similarity; + removedSet.insert(it.key()); + matchedHash.remove(it.key()); + } else { + removedSet.insert(fcnA); + } + } + } + const RzAnalysisFunction *func = nullptr; - setFunctionDescription(&desc.original, fcnA); - setFunctionDescription(&desc.modified, fcnB); - - desc.simtype = RZ_ANALYSIS_SIMILARITY_TYPE_STR(pair->similarity); - desc.similarity = pair->similarity; - - pairs.push_back(desc); + CutterRzListForeach (result->unmatch_a, it, RzAnalysisFunction, func) { + removedSet.insert(func); } + CutterRzListForeach (result->unmatch_b, it, RzAnalysisFunction, func) { + addedSet.insert(func); + } +} - return pairs; +QList BinDiff::matches() +{ + return matchedList; } QList BinDiff::mismatch(bool originalFile) { QList list; - if (!result) { - return list; - } - - const RzAnalysisFunction *func = nullptr; - const RzList *unmatch = originalFile ? result->unmatch_a : result->unmatch_b; - const RzListIter *it = nullptr; - - CutterRzListForeach (unmatch, it, RzAnalysisFunction, func) { + for (const RzAnalysisFunction *func : (originalFile ? removedSet : addedSet)) { FunctionDescription desc; setFunctionDescription(&desc, func); list.push_back(desc); } - return list; } diff --git a/src/core/BinDiff.h b/src/core/BinDiff.h index 29906b7527..ed07ef8f79 100644 --- a/src/core/BinDiff.h +++ b/src/core/BinDiff.h @@ -46,6 +46,10 @@ public slots: private: std::unique_ptr cutterDiff; RzAnalysisMatchResult *result; + void sortFunctions(); + QList matchedList; + QSet removedSet; + QSet addedSet; bool continueRun; size_t maxTotal; #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) diff --git a/src/core/CutterDiff.cpp b/src/core/CutterDiff.cpp index 14e3d2b3e3..dd198e3648 100644 --- a/src/core/CutterDiff.cpp +++ b/src/core/CutterDiff.cpp @@ -32,6 +32,15 @@ bool CutterDiff::initCores() coreA = rz_core_new(); coreB = rz_core_new(); + // reassigning console to the main cutter core till the context based console is ready + { + RzCoreLocked core(Core()); + core->cons->line->user = core; + core->cons->line->cb_fkey = core->cons->cb_fkey; + core->cons->user_fgets_user = core; + rz_core_bind_cons(core); + } + if (!(coreA || coreB)) { goto fail; } @@ -41,6 +50,7 @@ bool CutterDiff::initCores() coreA->print->scr_prompt = false; coreB->print->scr_prompt = false; + return true; fail: qWarning() << "Core initialization has failed undefined behaviour expected."; @@ -87,7 +97,6 @@ bool CutterDiff::openFiles(const QString &fileA, const QString &fileB) filePathB = fileB; fileNameA = QFileInfo(filePathA).fileName(); fileNameB = QFileInfo(filePathB).fileName(); - syncConfig(); return true; fail: rz_core_file_close_all_but(coreA); diff --git a/src/tools/bindiff/CutterDiffWindow.cpp b/src/tools/bindiff/CutterDiffWindow.cpp index 2fad3f2b9e..2338645da0 100644 --- a/src/tools/bindiff/CutterDiffWindow.cpp +++ b/src/tools/bindiff/CutterDiffWindow.cpp @@ -66,7 +66,7 @@ QVariant FunctionListModel::data(const QModelIndex &index, int role) const case Name: return list->at(index.row()).name; case Offset: - return list->at(index.row()).offset; + return rzAddressString(list->at(index.row()).offset); default: return "unknown"; } @@ -75,7 +75,7 @@ QVariant FunctionListModel::data(const QModelIndex &index, int role) const case Name: return list->at(index.row()).name; case Offset: - return list->at(index.row()).offset; + return rzAddressString(list->at(index.row()).offset); default: return "unknown"; } @@ -242,12 +242,34 @@ QColor DiffMatchModel::gradientByRatio(const double ratio) const } DiffMismatchModel::DiffMismatchModel(QList *list, QObject *parent) - : QAbstractListModel(parent), list(list) + : AddressableItemModel(parent), list(list) { } DiffMismatchModel::~DiffMismatchModel() {} +QModelIndex DiffMismatchModel::index(int row, int column, const QModelIndex &parent) const +{ + if (parent.isValid()) { + return QModelIndex(); + } + + if (row < 0 || row >= list->size()) { + return QModelIndex(); + } + + if (column < 0 || column >= ColumnCount) { + return QModelIndex(); + } + + return createIndex(row, column); +} + +QModelIndex DiffMismatchModel::parent(const QModelIndex &) const +{ + return QModelIndex(); +} + int DiffMismatchModel::rowCount(const QModelIndex &) const { return list->count(); @@ -329,6 +351,11 @@ QVariant DiffMismatchModel::headerData(int section, Qt::Orientation, int role) c } } +RVA DiffMismatchModel::address(const QModelIndex &index) const +{ + return list->at(index.row()).offset; +} + CutterDiffWindow::CutterDiffWindow(std::unique_ptr bDiff, QWidget *parent) : QMainWindow(parent), ui(new Ui::CutterDiffWindow), @@ -343,8 +370,8 @@ CutterDiffWindow::CutterDiffWindow(std::unique_ptr bDiff, QWidget *pare &CutterDiffWindow::showContextMenuMatches); connect(ui->treeViewMatches, &CutterTreeView::clicked, this, &CutterDiffWindow::selectFunction); // connect(bDiff, &BinDiff::complete, this, &CutterDiffWindow::onBinDiffCompleted); - // connect(ui->actionDiffNewFiles, &QAction::triggered, this, - // &CutterDiffWindow::onActionDiffNewFile); + connect(ui->actionDiffNewFiles, &QAction::triggered, this, + &CutterDiffWindow::onActionDiffNewFile); ui->tabParsing->hide(); setupFonts(); showMaximized(); @@ -376,31 +403,46 @@ void CutterDiffWindow::showContextMenuMatches(const QPoint &pos) if (selected == seekTo) { if (index.column() < DiffMatchModel::AddressMod) { - hexDiff->seek(addr.first); + seekAndShowHexDiff({ addr.first, RVA_INVALID }); } else { - hexDiff->seek(addr.second, false); + seekAndShowHexDiff({ RVA_INVALID, addr.second }); } - ui->tabWidget->setCurrentIndex(3); } else if (selected == goToAndAlign) { - if (addr.first > addr.second) { - hexDiff->transpose(0, -static_cast(addr.first - addr.second), true); - } else { - hexDiff->transpose(0, static_cast(addr.second - addr.first), true); - } - ui->tabWidget->setCurrentIndex(3); - hexDiff->seek(addr.first); + seekAndShowHexDiff(addr); } else if (selected == diffFunctionLines) { lineDiff->fetchFunctionDisas(addr.first, addr.second); ui->tabWidget->setCurrentIndex(4); } else if (selected == copyAddress) { if (index.column() < DiffMatchModel::AddressMod) { - QApplication::clipboard()->setText(QString::number(addr.first)); + QApplication::clipboard()->setText(rzAddressString(addr.first)); } else { - QApplication::clipboard()->setText(QString::number(addr.second)); + QApplication::clipboard()->setText(rzAddressString(addr.second)); } } } +void CutterDiffWindow::seekAndShowHexDiff(QPair addr) +{ + if (!hexDiff) { + return; + } + ui->tabWidget->setCurrentIndex(3); + if (addr.first == RVA_INVALID && addr.second == RVA_INVALID) { + return; + } + + if (addr.second == RVA_INVALID) { + hexDiff->seek(addr.first, true); + } else if (addr.first == RVA_INVALID) { + hexDiff->seek(addr.second, false); + } else { + const int transpose = addr.first > addr.second ? -static_cast(addr.first - addr.second) + : static_cast(addr.second - addr.first); + hexDiff->transpose(0, transpose, true); + hexDiff->seek(addr.first); + } +} + void CutterDiffWindow::setupFonts() {} void CutterDiffWindow::showDiff() @@ -438,15 +480,41 @@ void CutterDiffWindow::showDiff() modelA = new FunctionListModel(&fcnsA, this); modelB = new FunctionListModel(&fcnsB, this); + const QFontMetrics fm(ui->fcnsALabel->font()); ui->treeViewFcnsA->setModel(modelA); - ui->fcnsALabel->setText(cutterDiff->getFileName(true)); + ui->fcnsALabel->setText( + fm.elidedText(cutterDiff->getFileName(true), Qt::ElideRight, ui->fcnsALabel->width())); + ui->labelInfoA->setText( + fm.elidedText(cutterDiff->getFileName(true), Qt::ElideRight, ui->labelInfoA->width())); ui->treeViewFcnsB->setModel(modelB); - ui->fcnsBLabel->setText(cutterDiff->getFileName(false)); + ui->fcnsBLabel->setText( + fm.elidedText(cutterDiff->getFileName(false), Qt::ElideRight, ui->fcnsBLabel->width())); + ui->labelInfoB->setText( + fm.elidedText(cutterDiff->getFileName(false), Qt::ElideRight, ui->labelInfoB->width())); addHexDiff(); - connect(ui->treeViewFcnsA, &CutterTreeView::clicked, this, - [this](const QModelIndex &index) { hexDiff->seek(modelA->address(index), true); }); - connect(ui->treeViewFcnsB, &CutterTreeView::clicked, this, - [this](const QModelIndex &index) { hexDiff->seek(modelB->address(index), false); }); + connect(ui->treeViewFcnsA, &CutterTreeView::clicked, this, [this](const QModelIndex &index) { + seekAndShowHexDiff({ modelA->address(index), RVA_INVALID }); + }); + connect(ui->treeViewFcnsB, &CutterTreeView::clicked, this, [this](const QModelIndex &index) { + seekAndShowHexDiff({ RVA_INVALID, modelB->address(index) }); + }); + connect(ui->treeViewAdded, &CutterTreeView::doubleClicked, this, + [this](const QModelIndex &index) { + seekAndShowHexDiff({ RVA_INVALID, added->address(index) }); + }); + connect(ui->treeViewRemoved, &CutterTreeView::doubleClicked, this, + [this](const QModelIndex &index) { + seekAndShowHexDiff({ RVA_INVALID, removed->address(index) }); + }); + connect(ui->treeViewMatches, &CutterTreeView::doubleClicked, this, + [this](const QModelIndex &index) { + auto addr = matches->address(index); + if (index.column() < DiffMatchModel::AddressMod) { + seekAndShowHexDiff({ addr.first, RVA_INVALID }); + } else { + seekAndShowHexDiff({ RVA_INVALID, addr.second }); + } + }); // Transpose @@ -489,11 +557,12 @@ void CutterDiffWindow::showDiff() connect(hexDiff, &HexDiff::selectionChanged, this, &CutterDiffWindow::selectionChanged); } -// void CutterDiffWindow::onActionDiffNewFile() -// { -// auto loadDiff = new DiffLoadDialog(bDiff, this); -// loadDiff->show(); -// } +void CutterDiffWindow::onActionDiffNewFile() +{ + auto loadDiff = new DiffLoadDialog(parentWidget()); + loadDiff->show(); + loadDiff->raise(); +} void CutterDiffWindow::addHexDiff() { diff --git a/src/tools/bindiff/CutterDiffWindow.h b/src/tools/bindiff/CutterDiffWindow.h index de23c93cd1..8cd6674172 100644 --- a/src/tools/bindiff/CutterDiffWindow.h +++ b/src/tools/bindiff/CutterDiffWindow.h @@ -60,7 +60,7 @@ class DiffMatchModel : public QAbstractListModel QColor perfect, partial; }; -class DiffMismatchModel : public QAbstractListModel +class DiffMismatchModel : public AddressableItemModel<> { Q_OBJECT @@ -82,12 +82,16 @@ class DiffMismatchModel : public QAbstractListModel DiffMismatchModel(QList *list, QObject *parent = nullptr); ~DiffMismatchModel(); + QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - - int rowCount(const QModelIndex &parent = QModelIndex()) const; - int columnCount(const QModelIndex &parent = QModelIndex()) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + RVA address(const QModelIndex &index) const override; private: QList *list; @@ -125,8 +129,10 @@ class CutterDiffWindow : public QMainWindow public: explicit CutterDiffWindow(std::unique_ptr bDiff, QWidget *parent = nullptr); ~CutterDiffWindow(); + void seekAndShowHexDiff(QPair addr); public slots: // void onBinDiffCompleted(); + void onActionDiffNewFile(); private slots: void onCopyMD5AClicked(); void onCopyShA1AClicked(); diff --git a/src/tools/bindiff/CutterDiffWindow.ui b/src/tools/bindiff/CutterDiffWindow.ui index 30a2c0dc9d..8faf8e3af6 100644 --- a/src/tools/bindiff/CutterDiffWindow.ui +++ b/src/tools/bindiff/CutterDiffWindow.ui @@ -102,7 +102,7 @@ - 4 + 0 @@ -110,7 +110,17 @@ - + + + 0 + + + false + + + false + + @@ -120,7 +130,17 @@ - + + + 0 + + + false + + + false + + @@ -130,7 +150,17 @@ - + + + 0 + + + false + + + false + + @@ -265,7 +295,7 @@ - + INFO A @@ -491,7 +521,7 @@ - + INFO B diff --git a/src/tools/bindiff/DiffLoadDialog.cpp b/src/tools/bindiff/DiffLoadDialog.cpp index a1a2bffe05..e56377e5b6 100644 --- a/src/tools/bindiff/DiffLoadDialog.cpp +++ b/src/tools/bindiff/DiffLoadDialog.cpp @@ -2,6 +2,7 @@ #include "ui_DiffLoadDialog.h" +#include #include #include #include @@ -16,10 +17,10 @@ DiffLoadDialog::DiffLoadDialog(QWidget *parent) : QDialog(parent), ui(new Ui::Di setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); setModal(true); - ui->lineEditFileA->setReadOnly(true); + // ui->lineEditFileA->setReadOnly(true); ui->lineEditFileA->setText(""); - ui->lineEditFileB->setReadOnly(true); + // ui->lineEditFileB->setReadOnly(true); ui->lineEditFileB->setText(""); ui->comboBoxAnalysis->addItem(tr("Basic")); @@ -35,6 +36,10 @@ DiffLoadDialog::DiffLoadDialog(QWidget *parent) : QDialog(parent), ui(new Ui::Di connect(ui->buttonFileBOpen, &QPushButton::clicked, this, &DiffLoadDialog::onButtonFileBOpenClicked); connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &DiffLoadDialog::onButtonBoxAccepted); + connect(ui->setCurrentA, &QCheckBox::checkStateChanged, this, + &DiffLoadDialog::onSetCurrentAChanged); + connect(ui->setCurrentB, &QCheckBox::checkStateChanged, this, + &DiffLoadDialog::onSetCurrentBChanged); auto index = ui->comboBoxAnalysis->findData(tr("Auto"), Qt::DisplayRole); ui->comboBoxAnalysis->setCurrentIndex(index); @@ -42,6 +47,32 @@ DiffLoadDialog::DiffLoadDialog(QWidget *parent) : QDialog(parent), ui(new Ui::Di DiffLoadDialog::~DiffLoadDialog() {} +void DiffLoadDialog::onSetCurrentAChanged(Qt::CheckState state) +{ + if (state) { + ui->lineEditFileA->setText(Core()->getConfig("file.path")); + ui->setCurrentB->setDisabled(true); + ui->buttonFileAOpen->setDisabled(true); + } else { + ui->lineEditFileA->setText(""); + ui->setCurrentB->setDisabled(false); + ui->buttonFileAOpen->setDisabled(false); + } +} + +void DiffLoadDialog::onSetCurrentBChanged(Qt::CheckState state) +{ + if (state) { + ui->lineEditFileB->setText(Core()->getConfig("file.path")); + ui->setCurrentA->setDisabled(true); + ui->buttonFileBOpen->setDisabled(true); + } else { + ui->lineEditFileB->setText(""); + ui->setCurrentA->setDisabled(false); + ui->buttonFileBOpen->setDisabled(false); + } +} + QString DiffLoadDialog::getFileA() const { return ui->lineEditFileA->text(); @@ -104,13 +135,6 @@ void DiffLoadDialog::onButtonFileBOpenClicked() if (fileName.isEmpty()) { return; } - const QFileInfo info(fileName); - - if (!info.exists() || !info.isFile()) { - QMessageBox::warning(this, tr("Invalid Path"), - tr("Given file path for File B is not valid.")); - return; - } ui->lineEditFileB->setText(fileName); } @@ -121,14 +145,28 @@ void DiffLoadDialog::onButtonBoxAccepted() QMessageBox::warning(this, tr("Empty FileA"), tr("Select a file for diffing.")); return; } + const QFileInfo infoA(ui->lineEditFileA->text()); + + if (!infoA.exists() || !infoA.isFile()) { + QMessageBox::warning(this, tr("Invalid Path"), + tr("Given file path for File A is not valid.")); + return; + } if (ui->lineEditFileB->text().isEmpty()) { QMessageBox::warning(this, tr("Empty FileB"), tr("Select a file for diffing.")); return; } - auto waitDialog = new DiffWaitDialog(parentWidget()); + const QFileInfo infoB(ui->lineEditFileB->text()); + + if (!infoB.exists() || !infoB.isFile()) { + QMessageBox::warning(this, tr("Invalid Path"), + tr("Given file path for File B is not valid.")); + return; + } + auto waitDialog = new DiffWaitDialog(); waitDialog->show(ui->lineEditFileA->text(), ui->lineEditFileB->text(), ui->comboBoxAnalysis->currentIndex(), ui->comboBoxCompare->currentIndex()); - emit startDiffing(); + waitDialog->setAttribute(Qt::WA_DeleteOnClose); } void DiffLoadDialog::onButtonBoxRejected() {} diff --git a/src/tools/bindiff/DiffLoadDialog.h b/src/tools/bindiff/DiffLoadDialog.h index f373af5fbc..bc33a02d69 100644 --- a/src/tools/bindiff/DiffLoadDialog.h +++ b/src/tools/bindiff/DiffLoadDialog.h @@ -34,6 +34,8 @@ private slots: void onButtonFileBOpenClicked(); void onButtonBoxAccepted(); void onButtonBoxRejected(); + void onSetCurrentAChanged(Qt::CheckState state); + void onSetCurrentBChanged(Qt::CheckState state); private: std::unique_ptr ui; diff --git a/src/tools/bindiff/DiffLoadDialog.ui b/src/tools/bindiff/DiffLoadDialog.ui index e1b6e4e9e2..7a20cb3ba6 100644 --- a/src/tools/bindiff/DiffLoadDialog.ui +++ b/src/tools/bindiff/DiffLoadDialog.ui @@ -31,54 +31,61 @@ QLayout::SizeConstraint::SetMinimumSize - - - - - + + + + + 0 + 0 + + + + <html><head/><body><p>File to compare to.</p></body></html> + - Select + File 1(required) - - + + + + + - Select + Compare Logic - - + + - - + + - - - - - 0 - 0 - - - - <html><head/><body><p>You can load a previous saved diff instead of performing a new diff scan.</p></body></html> + + + + Analysis Level + + + + - File 2(required) + Select - - + + - Analysis Level + Current file - - + + 0 @@ -86,22 +93,29 @@ - <html><head/><body><p>File to compare to.</p></body></html> + <html><head/><body><p>You can load a previous saved diff instead of performing a new diff scan.</p></body></html> - File 1(required) + File 2(required) - - + + - Compare Logic + Select + + + - + + + Current file + + diff --git a/src/tools/bindiff/DiffWaitDialog.cpp b/src/tools/bindiff/DiffWaitDialog.cpp index 0a99f2153c..9ea5bfcb9a 100644 --- a/src/tools/bindiff/DiffWaitDialog.cpp +++ b/src/tools/bindiff/DiffWaitDialog.cpp @@ -79,7 +79,8 @@ void DiffWaitDialog::onProgress(BinDiffStatusDescription status) void DiffWaitDialog::onCompletion() { timer.stop(); - auto *diffWindow = new CutterDiffWindow(std::move(bDiff), parentWidget()); + auto *diffWindow = new CutterDiffWindow(std::move(bDiff)); + diffWindow->setAttribute(Qt::WA_DeleteOnClose); diffWindow->show(); close(); } diff --git a/src/tools/bindiff/DiffWaitDialog.ui b/src/tools/bindiff/DiffWaitDialog.ui index 9d86f65a38..8b2b89d712 100644 --- a/src/tools/bindiff/DiffWaitDialog.ui +++ b/src/tools/bindiff/DiffWaitDialog.ui @@ -3,7 +3,7 @@ DiffWaitDialog - Qt::NonModal + Qt::WindowModality::NonModal @@ -20,16 +20,16 @@ - Performing binary diffing + Performing binary diffing - QLayout::SetMinimumSize + QLayout::SizeConstraint::SetMinimumSize - QLayout::SetMinimumSize + QLayout::SizeConstraint::SetMinimumSize @@ -105,10 +105,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel + QDialogButtonBox::StandardButton::Cancel diff --git a/src/tools/bindiff/HexDiff.cpp b/src/tools/bindiff/HexDiff.cpp index bc1e17fa8b..7879548f97 100644 --- a/src/tools/bindiff/HexDiff.cpp +++ b/src/tools/bindiff/HexDiff.cpp @@ -387,8 +387,7 @@ void HexDiff::updateColors() printableColor = Config()->getColor("ai.write"); defColor = Config()->getColor("btext"); addrColor = Config()->getColor("func_var_addr"); - diffColor = Config()->getColor("graph.diff.unmatch"); - warningColor = QColor("red"); + diffColor = Config()->getColor("diff.unmatch"); updateCursorMeta(); updateViewport(); @@ -767,7 +766,6 @@ void HexDiff::copy() return; } QString x; - qInfo() << cursorArea; if (cursorArea < 2) { if (cursorArea == DiffArea::AsciiA) { x = QString::fromUtf8( @@ -1011,11 +1009,11 @@ void HexDiff::drawItemArea(QPainter &painter, DiffFileContext &ctx) } if (ctx.file == DiffFile::A && diffItemsAt(itemAddr)) { - itemColor = warningColor; + itemColor = diffColor; } if (ctx.file == DiffFile::B && diffItemsAt(getAddressA(itemAddr))) { - itemColor = warningColor; + itemColor = diffColor; } painter.setPen(itemColor); @@ -1067,11 +1065,11 @@ void HexDiff::drawAsciiArea(QPainter &painter, DiffFileContext &ctx) color = palette().highlightedText().color(); } if (ctx.file == DiffFile::A && diffItemsAt(address)) { - color = warningColor; + color = diffColor; } if (ctx.file == DiffFile::B && diffItemsAt(getAddressA(address))) { - color = warningColor; + color = diffColor; } painter.setPen(color); /* Dots look ugly. Use fillRect() instead of drawText(). */ diff --git a/src/tools/bindiff/HexDiff.h b/src/tools/bindiff/HexDiff.h index 1dd2d5719e..d768f5fa7f 100644 --- a/src/tools/bindiff/HexDiff.h +++ b/src/tools/bindiff/HexDiff.h @@ -534,7 +534,6 @@ private slots: QColor b0x7fColor; QColor b0xffColor; QColor printableColor; - QColor warningColor; // warning Color is used instead of the actual Diff Color HexdumpRangeDialog rangeDialog; From d0a87a38d4dad6f01eeabc444e1d8b2dead2e8b1 Mon Sep 17 00:00:00 2001 From: NewtronReal Date: Mon, 13 Jul 2026 09:34:48 +0530 Subject: [PATCH 12/13] Function matching fix --- src/core/BinDiff.cpp | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/core/BinDiff.cpp b/src/core/BinDiff.cpp index 11f11daf6e..5e39c6ab57 100644 --- a/src/core/BinDiff.cpp +++ b/src/core/BinDiff.cpp @@ -104,20 +104,25 @@ void BinDiff::sortFunctions() if (!result) { return; } + struct MatchEntry + { + const RzAnalysisFunction *fcnA; + long long int index; + }; // Get similar pairs injectively const RzAnalysisMatchPair *pair = nullptr; const RzListIter *it = nullptr; const RzAnalysisFunction *fcnA = nullptr; const RzAnalysisFunction *fcnB = nullptr; - QHash matchedHash; + QHash matchesByB; CutterRzListForeach (result->matches, it, RzAnalysisMatchPair, pair) { BinDiffMatchDescription desc; fcnA = static_cast(pair->pair_a); fcnB = static_cast(pair->pair_b); - auto it = matchedHash.find(fcnB); - if (it == matchedHash.end()) { + auto it = matchesByB.find(fcnB); + if (it == matchesByB.end()) { setFunctionDescription(&desc.original, fcnA); setFunctionDescription(&desc.modified, fcnB); @@ -125,17 +130,18 @@ void BinDiff::sortFunctions() desc.similarity = pair->similarity; matchedList.push_back(desc); - matchedHash[fcnB] = matchedList.size() - 1; + matchesByB[fcnB] = MatchEntry { fcnA, matchedList.size() - 1 }; if (removedSet.contains(fcnA)) { removedSet.remove(fcnA); } } else { - if (matchedList[it.value()].similarity < pair->similarity) { - setFunctionDescription(&matchedList[it.value()].original, fcnA); - matchedList[it.value()].simtype = RZ_ANALYSIS_SIMILARITY_TYPE_STR(pair->similarity); - matchedList[it.value()].similarity = pair->similarity; - removedSet.insert(it.key()); - matchedHash.remove(it.key()); + if (matchedList[it.value().index].similarity < pair->similarity) { + setFunctionDescription(&matchedList[it.value().index].original, fcnA); + matchedList[it.value().index].simtype = + RZ_ANALYSIS_SIMILARITY_TYPE_STR(pair->similarity); + matchedList[it.value().index].similarity = pair->similarity; + removedSet.insert(it.value().fcnA); + matchesByB.remove(it.value().fcnA); } else { removedSet.insert(fcnA); } From 2203632d2b229bb652db4d3895190cd80a37709c Mon Sep 17 00:00:00 2001 From: NewtronReal Date: Tue, 21 Jul 2026 10:54:11 +0530 Subject: [PATCH 13/13] Split and Unified views for LineDiff --- src/core/BinDiff.cpp | 87 ++++++----- src/core/CutterDiff.cpp | 21 ++- src/core/CutterDiff.h | 7 + src/tools/bindiff/CutterDiffWindow.cpp | 2 +- src/tools/bindiff/LineDiffWidget.cpp | 192 +++++++++++++++++++++---- src/tools/bindiff/LineDiffWidget.h | 20 ++- 6 files changed, 261 insertions(+), 68 deletions(-) diff --git a/src/core/BinDiff.cpp b/src/core/BinDiff.cpp index 5e39c6ab57..eb787b7c6d 100644 --- a/src/core/BinDiff.cpp +++ b/src/core/BinDiff.cpp @@ -99,54 +99,69 @@ static void setFunctionDescription(FunctionDescription *desc, const RzAnalysisFu desc->stackframe = func->maxstack; } +struct MatchEntry +{ + const RzAnalysisFunction *fcnA; + const RzAnalysisFunction *fcnB; + double similarity; +}; + void BinDiff::sortFunctions() { if (!result) { return; } - struct MatchEntry - { - const RzAnalysisFunction *fcnA; - long long int index; - }; - // Get similar pairs injectively + + matchedList.clear(); + removedSet.clear(); + addedSet.clear(); + + QHash bestMatches; + QSet discardedA; + const RzAnalysisMatchPair *pair = nullptr; const RzListIter *it = nullptr; - const RzAnalysisFunction *fcnA = nullptr; - const RzAnalysisFunction *fcnB = nullptr; - - QHash matchesByB; CutterRzListForeach (result->matches, it, RzAnalysisMatchPair, pair) { - BinDiffMatchDescription desc; - fcnA = static_cast(pair->pair_a); - fcnB = static_cast(pair->pair_b); - auto it = matchesByB.find(fcnB); - if (it == matchesByB.end()) { - setFunctionDescription(&desc.original, fcnA); - setFunctionDescription(&desc.modified, fcnB); - - desc.simtype = RZ_ANALYSIS_SIMILARITY_TYPE_STR(pair->similarity); - desc.similarity = pair->similarity; - - matchedList.push_back(desc); - matchesByB[fcnB] = MatchEntry { fcnA, matchedList.size() - 1 }; - if (removedSet.contains(fcnA)) { - removedSet.remove(fcnA); - } + + auto *fcnA = static_cast(pair->pair_a); + auto *fcnB = static_cast(pair->pair_b); + + auto hashIt = bestMatches.find(fcnB); + + if (hashIt == bestMatches.end()) { + bestMatches.insert(fcnB, { fcnA, fcnB, pair->similarity }); + continue; + } + + if (pair->similarity > hashIt->similarity) { + + discardedA.insert(hashIt->fcnA); + + discardedA.remove(fcnA); + + hashIt->fcnA = fcnA; + hashIt->similarity = pair->similarity; } else { - if (matchedList[it.value().index].similarity < pair->similarity) { - setFunctionDescription(&matchedList[it.value().index].original, fcnA); - matchedList[it.value().index].simtype = - RZ_ANALYSIS_SIMILARITY_TYPE_STR(pair->similarity); - matchedList[it.value().index].similarity = pair->similarity; - removedSet.insert(it.value().fcnA); - matchesByB.remove(it.value().fcnA); - } else { - removedSet.insert(fcnA); - } + + discardedA.insert(fcnA); } } + + for (const auto &entry : std::as_const(bestMatches)) { + + BinDiffMatchDescription desc; + + setFunctionDescription(&desc.original, entry.fcnA); + setFunctionDescription(&desc.modified, entry.fcnB); + + desc.similarity = entry.similarity; + desc.simtype = RZ_ANALYSIS_SIMILARITY_TYPE_STR(entry.similarity); + + matchedList.push_back(std::move(desc)); + } + removedSet = std::move(discardedA); + const RzAnalysisFunction *func = nullptr; CutterRzListForeach (result->unmatch_a, it, RzAnalysisFunction, func) { diff --git a/src/core/CutterDiff.cpp b/src/core/CutterDiff.cpp index dd198e3648..1d81911579 100644 --- a/src/core/CutterDiff.cpp +++ b/src/core/CutterDiff.cpp @@ -482,6 +482,25 @@ RzDiff *CutterDiff::lineDiff(const QString &lines1, const QString &lines2) CutterRzList*/> CutterDiff::lineDiffOpsGrouped(RzDiff *diff) const { - auto *groups = rz_diff_unified_text_grouped(diff); + auto *groups = rz_diff_opcodes_grouped_new(diff, 2); return CutterRzList*/>(groups); } + +Bound CutterDiff::getLineDiffBounds(const QString &line1, const QString &line2) +{ + if (line1.size() != line2.size()) { + return { 0, 0 }; + } + + int first = 0; + while (first < line1.size() && line1[first] == line2[first]) { + ++first; + } + + int last = line1.size() - 1; + while (last >= first && line1[last] == line2[last]) { + --last; + } + + return { first, last - first + 1 }; +} diff --git a/src/core/CutterDiff.h b/src/core/CutterDiff.h index 08ba53583f..90bad93359 100644 --- a/src/core/CutterDiff.h +++ b/src/core/CutterDiff.h @@ -11,6 +11,12 @@ class CutterDiffLocked; +struct Bound +{ + int pos; + int size; +}; + class CUTTER_EXPORT CutterDiff : public QObject { Q_OBJECT @@ -65,6 +71,7 @@ class CUTTER_EXPORT CutterDiff : public QObject CutterRzList*/> lineDiffOpsGrouped(RzDiff *diff) const; QString getFileName(bool orig = true) const { return orig ? fileNameA : fileNameB; } QString getFilePath(bool orig = true) const { return orig ? filePathA : filePathB; } + Bound getLineDiffBounds(const QString &line1, const QString &line2); private: RzCore *coreA = nullptr; diff --git a/src/tools/bindiff/CutterDiffWindow.cpp b/src/tools/bindiff/CutterDiffWindow.cpp index 2338645da0..44171fc011 100644 --- a/src/tools/bindiff/CutterDiffWindow.cpp +++ b/src/tools/bindiff/CutterDiffWindow.cpp @@ -410,7 +410,7 @@ void CutterDiffWindow::showContextMenuMatches(const QPoint &pos) } else if (selected == goToAndAlign) { seekAndShowHexDiff(addr); } else if (selected == diffFunctionLines) { - lineDiff->fetchFunctionDisas(addr.first, addr.second); + lineDiff->fetchFunctionDisasSplit(addr.first, addr.second); ui->tabWidget->setCurrentIndex(4); } else if (selected == copyAddress) { if (index.column() < DiffMatchModel::AddressMod) { diff --git a/src/tools/bindiff/LineDiffWidget.cpp b/src/tools/bindiff/LineDiffWidget.cpp index 14b9a36423..0924c91399 100644 --- a/src/tools/bindiff/LineDiffWidget.cpp +++ b/src/tools/bindiff/LineDiffWidget.cpp @@ -1,74 +1,182 @@ #include "LineDiffWidget.h" +#include +#include #include #include #include #include #include +#include + LineDiffWidget::LineDiffWidget(CutterDiff *cutterDiff, QWidget *parent) : QWidget(parent), cutterDiff(cutterDiff), leftEdit(new DiffTextEdit(this)), - rightEdit(new DiffTextEdit(this)) + rightEdit(new DiffTextEdit(this)), + unifiedEdit(new DiffTextEdit(this)), + viewSelector(new QComboBox(this)), + splitViewSplitter(new QSplitter(Qt::Horizontal, this)) { auto *layoutV = new QVBoxLayout(this); layoutV->setContentsMargins(0, 0, 0, 0); layoutV->setSpacing(0); + auto *layoutHEdits = new QHBoxLayout(this); + layoutV->addLayout(layoutHEdits); + layoutHEdits->addWidget(unifiedEdit); + + splitViewSplitter->addWidget(leftEdit); + splitViewSplitter->addWidget(rightEdit); + layoutHEdits->addWidget(splitViewSplitter); + auto layoutH = new QHBoxLayout(this); + layoutH->addStretch(); + auto labelLineDiff = new QLabel(this); + labelLineDiff->setText("View : "); + layoutH->addWidget(labelLineDiff); + viewSelector->addItems({ "Unified", "Split", "Left", "Right" }); + connect(viewSelector, &QComboBox::currentIndexChanged, this, + &LineDiffWidget::onViewModeChanged); + + layoutH->addWidget(viewSelector); + layoutV->addLayout(layoutH); + + connect(leftEdit->verticalScrollBar(), &QScrollBar::valueChanged, this, + [this](int value) { rightEdit->verticalScrollBar()->setValue(value); }); + + connect(rightEdit->verticalScrollBar(), &QScrollBar::valueChanged, this, + [this](int value) { leftEdit->verticalScrollBar()->setValue(value); }); + setUpFonts(); + connect(Config(), &Configuration::fontsUpdated, this, &LineDiffWidget::setUpFonts); + onViewModeChanged(); +} - auto *splitter = new QSplitter(Qt::Horizontal, this); - splitter->addWidget(leftEdit); - splitter->addWidget(rightEdit); +LineDiffWidget::~LineDiffWidget() {} - layoutV->addWidget(splitter); +void LineDiffWidget::onViewModeChanged() +{ + splitViewSplitter->hide(); + unifiedEdit->hide(); + leftEdit->hide(); + rightEdit->hide(); + switch (viewSelector->currentIndex()) { + case 0: + unifiedEdit->show(); + break; + case 1: { + splitViewSplitter->show(); + leftEdit->show(); + rightEdit->show(); + break; + } + case 2: { + splitViewSplitter->show(); + leftEdit->show(); + rightEdit->hide(); + break; + } + case 3: { + splitViewSplitter->show(); + leftEdit->hide(); + rightEdit->show(); + break; + } + default: + break; + } } -LineDiffWidget::~LineDiffWidget() {} +void LineDiffWidget::setUpFonts() +{ + leftEdit->setUpFont(Config()->getFont()); + rightEdit->setUpFont(Config()->getFont()); + unifiedEdit->setUpFont(Config()->getFont()); +} -void LineDiffWidget::fetchFunctionDisas(RVA addrA, RVA addrB) +void LineDiffWidget::fetchFunctionDisasSplit(RVA addrA, RVA addrB) { + const QSignalBlocker blocker1(leftEdit), blocker2(rightEdit), blocker3(unifiedEdit); + leftEdit->clear(); + rightEdit->clear(); char *stringUtf; RzDiff *diffedLines = cutterDiff->diffFunctionDissas(addrA, addrB); + QColor matched = Config()->getColor("gui.match.perfect"); + QColor unmatched = Config()->getColor("gui.match.partial"); + matched.setAlpha(50); + unmatched.setAlpha(50); const CutterRzList*/> groups = cutterDiff->lineDiffOpsGrouped(diffedLines); for (const RzList /**/ *group : groups) { for (RzDiffOp *op : CutterRzList(group)) { + switch (op->type) { case RZ_DIFF_OP_EQUAL: { stringUtf = rz_diff_op_stringify(diffedLines, op, true); const QString opString = QString::fromUtf8(stringUtf); - leftEdit->insertPlainText(opString); - rightEdit->insertPlainText(opString); + leftEdit->insertFormatted(opString, { 0, 0, 0, 0 }); + rightEdit->insertFormatted(opString, { 0, 0, 0, 0 }); + unifiedEdit->insertFormatted(opString, { 0, 0, 0, 0 }); break; } case RZ_DIFF_OP_DELETE: { stringUtf = rz_diff_op_stringify(diffedLines, op, true); - leftEdit->insertFormatted(QString::fromUtf8(stringUtf), QColor(255, 0, 0, 100)); + leftEdit->insertFormatted(QString::fromUtf8(stringUtf), unmatched); + unifiedEdit->insertFormatted(QString::fromUtf8(stringUtf), unmatched); break; } case RZ_DIFF_OP_INSERT: { stringUtf = rz_diff_op_stringify(diffedLines, op, false); - rightEdit->insertFormatted(QString::fromUtf8(stringUtf), QColor(0, 255, 0, 100)); + rightEdit->insertFormatted(QString::fromUtf8(stringUtf), matched); + unifiedEdit->insertFormatted(QString::fromUtf8(stringUtf), matched); break; } case RZ_DIFF_OP_REPLACE: { - stringUtf = rz_diff_op_stringify(diffedLines, op, true); - leftEdit->insertFormatted(QString::fromUtf8(stringUtf), QColor(255, 0, 0, 100)); - stringUtf = rz_diff_op_stringify(diffedLines, op, false); - rightEdit->insertFormatted(QString::fromUtf8(stringUtf), QColor(0, 255, 0, 100)); + const QString actual = + QString::fromUtf8(rz_diff_op_stringify(diffedLines, op, true)); + const QString replaced = + QString::fromUtf8(rz_diff_op_stringify(diffedLines, op, false)); + const auto bound = cutterDiff->getLineDiffBounds(actual, replaced); + leftEdit->insertBounded(actual, unmatched, bound); + rightEdit->insertBounded(replaced, matched, bound); + unifiedEdit->insertBounded(actual, unmatched, bound); + unifiedEdit->insertBounded(replaced, matched, bound); break; } default: break; } + balanceLines(); } } rz_diff_free(diffedLines); } +void LineDiffWidget::balanceLines() +{ + const int leftLines = leftEdit->document()->blockCount(); + const int rightLines = rightEdit->document()->blockCount(); + + if (leftLines == rightLines) { + return; + } + + DiffTextEdit *edit = (leftLines < rightLines) ? leftEdit : rightEdit; + const int missing = std::abs(leftLines - rightLines); + + QTextCursor cursor(edit->document()); + cursor.movePosition(QTextCursor::End); + + for (int i = 0; i < missing; ++i) { + cursor.insertBlock(); + } + + edit->setTextCursor(cursor); +} + DiffTextEdit::DiffTextEdit(QWidget *parent) : QPlainTextEdit(parent) { setReadOnly(true); + setTextInteractionFlags(Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse); lineNumberArea = new LineNumberArea(this); connect(this, &QPlainTextEdit::blockCountChanged, this, &DiffTextEdit::updateLineNumberArea); @@ -77,26 +185,30 @@ DiffTextEdit::DiffTextEdit(QWidget *parent) : QPlainTextEdit(parent) connect(this, &QPlainTextEdit::cursorPositionChanged, this, &DiffTextEdit::highlightCurrentLine); + setLineWrapMode(QPlainTextEdit::NoWrap); + + setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); } DiffTextEdit::~DiffTextEdit() {} void DiffTextEdit::highlightCurrentLine() { - QList selections; - - QTextEdit::ExtraSelection selection; - - QColor lineColor = QColor(60, 60, 60); // Choose a theme-appropriate color - selection.format.setBackground(lineColor); - selection.format.setProperty(QTextFormat::FullWidthSelection, true); + const QTextBlock currentBlock = textCursor().block(); - selection.cursor = textCursor(); - selection.cursor.clearSelection(); + if (highlightedBlock.isValid() && highlightedBlock != currentBlock) { + QTextCursor oldCursor(highlightedBlock); + QTextBlockFormat oldFmt = highlightedBlock.blockFormat(); + oldFmt.clearBackground(); + oldCursor.setBlockFormat(oldFmt); + } - selections.append(selection); + QTextCursor cursor(currentBlock); + QTextBlockFormat fmt = currentBlock.blockFormat(); + fmt.setBackground(Config()->getColor("gui.background").lighter(100)); + cursor.setBlockFormat(fmt); - setExtraSelections(selections); + highlightedBlock = currentBlock; } void DiffTextEdit::resizeEvent(QResizeEvent *event) @@ -125,7 +237,7 @@ void DiffTextEdit::lineNumberAreaPaintEvent(QPaintEvent *event) { QPainter painter(lineNumberArea); - painter.fillRect(event->rect(), QColor(240, 240, 240)); + painter.fillRect(event->rect(), Config()->getColor("gui.background").darker(115)); QTextBlock block = firstVisibleBlock(); @@ -149,7 +261,7 @@ void DiffTextEdit::insertFormatted(const QString &text, const QColor &color) QTextCursor cursor = textCursor(); cursor.movePosition(QTextCursor::End); - const QTextCharFormat oldFormat = cursor.charFormat(); + const QTextCharFormat oldFormat = defaultFormat; QTextCharFormat fmt = oldFormat; fmt.setBackground(color); @@ -158,4 +270,28 @@ void DiffTextEdit::insertFormatted(const QString &text, const QColor &color) cursor.setCharFormat(oldFormat); setTextCursor(cursor); +} + +void DiffTextEdit::insertBounded(const QString &text, const QColor &color, const Bound bound) +{ + QTextCursor cursor = textCursor(); + cursor.movePosition(QTextCursor::End); + const QTextCharFormat oldFormat = defaultFormat; + const QColor colorHighlight = { color.red(), color.green(), color.blue(), 255 }; + QTextCharFormat fmt = oldFormat; + fmt.setBackground(color); + + cursor.insertText(text.left(bound.pos), fmt); + fmt.setBackground(colorHighlight); + cursor.insertText(text.mid(bound.pos, bound.size), fmt); + fmt.setBackground(color); + cursor.insertText(text.mid(bound.pos + bound.size), fmt); + cursor.setCharFormat(oldFormat); + setTextCursor(cursor); +} + +void DiffTextEdit::setUpFont(const QFont &font) +{ + setFont(font); + lineNumberArea->setFont(font); } \ No newline at end of file diff --git a/src/tools/bindiff/LineDiffWidget.h b/src/tools/bindiff/LineDiffWidget.h index 8f7bad8a6f..19f6d8a90e 100644 --- a/src/tools/bindiff/LineDiffWidget.h +++ b/src/tools/bindiff/LineDiffWidget.h @@ -1,7 +1,11 @@ #ifndef LINEDIFFWIDGET_H #define LINEDIFFWIDGET_H +#include +#include #include +#include +#include #include #include @@ -16,13 +20,21 @@ class LineDiffWidget : public QWidget public: explicit LineDiffWidget(CutterDiff *cutterDiff, QWidget *parent = nullptr); ~LineDiffWidget(); - void fetchFunctionDisas(RVA addrA, RVA addrB); + void fetchFunctionDisasSplit(RVA addrA, RVA addrB); + void balanceLines(); + void setUpFonts(); protected: +private slots: + void onViewModeChanged(); + private: CutterDiff *cutterDiff; DiffTextEdit *leftEdit; DiffTextEdit *rightEdit; + DiffTextEdit *unifiedEdit; + QComboBox *viewSelector; + QSplitter *splitViewSplitter; }; class DiffTextEdit : public QPlainTextEdit @@ -34,7 +46,9 @@ class DiffTextEdit : public QPlainTextEdit ~DiffTextEdit(); int lineNumberAreaWidth() const; void lineNumberAreaPaintEvent(QPaintEvent *event); - void insertFormatted(const QString &text, const QColor &color); + void insertFormatted(const QString &text, const QColor &color = QColor()); + void insertBounded(const QString &text, const QColor &color, const Bound bound); + void setUpFont(const QFont &font); protected: void resizeEvent(QResizeEvent *event) override; @@ -44,6 +58,8 @@ private slots: private: LineNumberArea *lineNumberArea; void highlightCurrentLine(); + const QTextCharFormat defaultFormat = textCursor().charFormat(); + QTextBlock highlightedBlock; }; class LineNumberArea : public QWidget