From fc94b896c4cf8c24251efb2edf565ca367e23a5a Mon Sep 17 00:00:00 2001 From: fcasal Date: Fri, 22 Jun 2018 09:45:00 +0100 Subject: [PATCH] Added Breakpoint widget (#546) * Added breakpoint widget --- src/Cutter.cpp | 51 ++++++++ src/Cutter.h | 18 ++- src/Cutter.pro | 9 +- src/MainWindow.cpp | 8 +- src/MainWindow.h | 1 + src/MainWindow.ui | 9 ++ src/widgets/BreakpointWidget.cpp | 197 +++++++++++++++++++++++++++++++ src/widgets/BreakpointWidget.h | 89 ++++++++++++++ src/widgets/BreakpointWidget.ui | 65 ++++++++++ 9 files changed, 441 insertions(+), 6 deletions(-) create mode 100644 src/widgets/BreakpointWidget.cpp create mode 100644 src/widgets/BreakpointWidget.h create mode 100644 src/widgets/BreakpointWidget.ui diff --git a/src/Cutter.cpp b/src/Cutter.cpp index 2a0b9f16..3fc01493 100644 --- a/src/Cutter.cpp +++ b/src/Cutter.cpp @@ -851,6 +851,57 @@ void CutterCore::addBreakpoint(RVA addr) { cmd("db " + RAddressString(addr)); emit instructionChanged(addr); + emit breakpointsChanged(); +} + +void CutterCore::delBreakpoint(RVA addr) +{ + cmd("db- " + RAddressString(addr)); + emit instructionChanged(addr); + emit breakpointsChanged(); +} + +void CutterCore::delAllBreakpoints() +{ + cmd("db-*"); + emit breakpointsChanged(); +} + +void CutterCore::enableBreakpoint(RVA addr) +{ + cmd("dbe " + RAddressString(addr)); + emit instructionChanged(addr); + emit breakpointsChanged(); +} + +void CutterCore::disableBreakpoint(RVA addr) +{ + cmd("dbd " + RAddressString(addr)); + emit instructionChanged(addr); + emit breakpointsChanged(); +} + +QList CutterCore::getBreakpoints() +{ + QList ret; + QJsonArray breakpointArray = cmdj("dbj").array(); + + for (QJsonValue value : breakpointArray) { + QJsonObject bpObject = value.toObject(); + + BreakpointDescription bp; + + bp.addr = bpObject["addr"].toVariant().toULongLong(); + bp.size = bpObject["size"].toVariant().toInt(); + bp.permission = bpObject["prot"].toString(); + bp.hw = bpObject["hw"].toBool(); + bp.trace = bpObject["trace"].toBool(); + bp.enabled = bpObject["enabled"].toBool(); + + ret << bp; + } + + return ret; } QJsonDocument CutterCore::getBacktrace() diff --git a/src/Cutter.h b/src/Cutter.h index 30e15ee5..6a18ba88 100644 --- a/src/Cutter.h +++ b/src/Cutter.h @@ -303,6 +303,15 @@ struct MemoryMapDescription { QString permission; }; +struct BreakpointDescription { + RVA addr; + int size; + QString permission; + bool hw; + bool trace; + bool enabled; +}; + Q_DECLARE_METATYPE(FunctionDescription) Q_DECLARE_METATYPE(ImportDescription) Q_DECLARE_METATYPE(ExportDescription) @@ -332,6 +341,7 @@ Q_DECLARE_METATYPE(ZignatureDescription) Q_DECLARE_METATYPE(SearchDescription) Q_DECLARE_METATYPE(SectionDescription) Q_DECLARE_METATYPE(MemoryMapDescription) +Q_DECLARE_METATYPE(BreakpointDescription) class CutterCore: public QObject { @@ -476,7 +486,11 @@ public: void continueUntilDebug(QString offset); void stepDebug(); void stepOverDebug(); - void addBreakpoint(RVA offset); + void addBreakpoint(RVA addr); + void delBreakpoint(RVA addr); + void delAllBreakpoints(); + void enableBreakpoint(RVA addr); + void disableBreakpoint(RVA addr); QString getActiveDebugPlugin(); QStringList getDebugPlugins(); void setDebugPlugin(QString plugin); @@ -536,6 +550,7 @@ public: QList getMemoryMap(); QList getAllSearch(QString search_for, QString space); BlockStatistics getBlockStatistics(unsigned int blocksCount); + QList getBreakpoints(); QList getXRefs(RVA addr, bool to, bool whole_function, const QString &filterType = QString::null); @@ -565,6 +580,7 @@ signals: void commentsChanged(); void registersChanged(); void instructionChanged(RVA offset); + void breakpointsChanged(); void notesChanged(const QString ¬es); void projectSaved(const QString &name); diff --git a/src/Cutter.pro b/src/Cutter.pro index 5f4a5004..5a258447 100644 --- a/src/Cutter.pro +++ b/src/Cutter.pro @@ -178,7 +178,8 @@ SOURCES += \ utils/R2Task.cpp \ widgets/DebugToolbar.cpp \ widgets/MemoryMapWidget.cpp \ - dialogs/preferences/DebugOptionsWidget.cpp + dialogs/preferences/DebugOptionsWidget.cpp \ + widgets/BreakpointWidget.cpp HEADERS += \ Cutter.h \ @@ -268,7 +269,8 @@ HEADERS += \ utils/R2Task.h \ widgets/DebugToolbar.h \ widgets/MemoryMapWidget.h \ - dialogs/preferences/DebugOptionsWidget.h + dialogs/preferences/DebugOptionsWidget.h \ + widgets/BreakpointWidget.h FORMS += \ dialogs/AboutDialog.ui \ @@ -319,7 +321,8 @@ FORMS += \ dialogs/OpenFileDialog.ui \ widgets/MemoryMapWidget.ui \ widgets/MemoryMapWidget.ui \ - dialogs/preferences/DebugOptionsWidget.ui + dialogs/preferences/DebugOptionsWidget.ui \ + widgets/BreakpointWidget.ui RESOURCES += \ resources.qrc \ diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 27b38ca3..72f5b141 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -75,6 +75,7 @@ #include "widgets/ZignaturesWidget.h" #include "widgets/DebugToolbar.h" #include "widgets/MemoryMapWidget.h" +#include "widgets/BreakpointWidget.h" // Graphics #include @@ -206,6 +207,7 @@ void MainWindow::initUI() backtraceDock = new BacktraceWidget(this, ui->actionBacktrace); registersDock = new RegistersWidget(this, ui->actionRegisters); memoryMapDock = new MemoryMapWidget(this, ui->actionMemoryMap); + breakpointDock = new BreakpointWidget(this, ui->actionBreakpoint); #ifdef CUTTER_ENABLE_JUPYTER jupyterDock = new JupyterWidget(this, ui->actionJupyter); #else @@ -545,8 +547,9 @@ void MainWindow::restoreDocks() addExtraWidget(stackDock); splitDockWidget(stackDock, registersDock, Qt::Vertical); splitDockWidget(stackDock, backtraceDock, Qt::Vertical); - // MemoryMap widget goes in the center tabs + // MemoryMap/Breakpoint widget goes in the center tabs tabifyDockWidget(dashboardDock, memoryMapDock); + tabifyDockWidget(dashboardDock, breakpointDock); #ifdef CUTTER_ENABLE_JUPYTER tabifyDockWidget(dashboardDock, jupyterDock); #endif @@ -629,7 +632,8 @@ void MainWindow::showDebugDocks() stackDock, registersDock, backtraceDock, - memoryMapDock + memoryMapDock, + breakpointDock }; for (auto w : dockWidgets) { if (debugDocks.contains(w)) { diff --git a/src/MainWindow.h b/src/MainWindow.h index 1474e098..2a4bb38f 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -226,6 +226,7 @@ private: QDockWidget *backtraceDock = nullptr; QDockWidget *memoryMapDock = nullptr; NewFileDialog *newFileDialog = nullptr; + QDockWidget *breakpointDock = nullptr; #ifdef CUTTER_ENABLE_JUPYTER JupyterWidget *jupyterDock = nullptr; #endif diff --git a/src/MainWindow.ui b/src/MainWindow.ui index 96d49168..e9c0f7f7 100644 --- a/src/MainWindow.ui +++ b/src/MainWindow.ui @@ -231,6 +231,7 @@ border-top: 0px; + @@ -1046,6 +1047,14 @@ border-top: 0px; Memory map + + + true + + + Breakpoints + + true diff --git a/src/widgets/BreakpointWidget.cpp b/src/widgets/BreakpointWidget.cpp new file mode 100644 index 00000000..61d18c55 --- /dev/null +++ b/src/widgets/BreakpointWidget.cpp @@ -0,0 +1,197 @@ +#include "BreakpointWidget.h" +#include "ui_BreakpointWidget.h" +#include "MainWindow.h" +#include "utils/Helpers.h" +#include + +BreakpointModel::BreakpointModel(QList *breakpoints, QObject *parent) + : QAbstractListModel(parent), + breakpoints(breakpoints) +{ +} + +int BreakpointModel::rowCount(const QModelIndex &) const +{ + return breakpoints->count(); +} + +int BreakpointModel::columnCount(const QModelIndex &) const +{ + return BreakpointModel::ColumnCount; +} + +QVariant BreakpointModel::data(const QModelIndex &index, int role) const +{ + if (index.row() >= breakpoints->count()) + return QVariant(); + + const BreakpointDescription &breakpoint = breakpoints->at(index.row()); + + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case AddrColumn: + return RAddressString(breakpoint.addr); + case PermColumn: + return breakpoint.permission; + case HwColumn: + return breakpoint.hw; + case TraceColumn: + return breakpoint.trace; + case EnabledColumn: + return breakpoint.enabled; + default: + return QVariant(); + } + case BreakpointDescriptionRole: + return QVariant::fromValue(breakpoint); + default: + return QVariant(); + } +} + +QVariant BreakpointModel::headerData(int section, Qt::Orientation, int role) const +{ + switch (role) { + case Qt::DisplayRole: + switch (section) { + case AddrColumn: + return tr("Offset"); + case PermColumn: + return tr("Permissions"); + case HwColumn: + return tr("Hardware bp"); + case TraceColumn: + return tr("Tracing"); + case EnabledColumn: + return tr("Active"); + default: + return QVariant(); + } + default: + return QVariant(); + } +} + +void BreakpointModel::beginReloadBreakpoint() +{ + beginResetModel(); +} + +void BreakpointModel::endReloadBreakpoint() +{ + endResetModel(); +} + +BreakpointProxyModel::BreakpointProxyModel(BreakpointModel *sourceModel, QObject *parent) + : QSortFilterProxyModel(parent) +{ + setSourceModel(sourceModel); +} + +bool BreakpointProxyModel::filterAcceptsRow(int row, const QModelIndex &parent) const +{ + QModelIndex index = sourceModel()->index(row, 0, parent); + BreakpointDescription item = index.data(BreakpointModel::BreakpointDescriptionRole).value(); + return item.permission.contains(filterRegExp()); +} + +bool BreakpointProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + BreakpointDescription leftBreakpt = left.data(BreakpointModel::BreakpointDescriptionRole).value(); + BreakpointDescription rightBreakpt = right.data(BreakpointModel::BreakpointDescriptionRole).value(); + + switch (left.column()) { + case BreakpointModel::AddrColumn: + return leftBreakpt.addr < rightBreakpt.addr; + case BreakpointModel::HwColumn: + return leftBreakpt.hw < rightBreakpt.hw; + case BreakpointModel::PermColumn: + return leftBreakpt.permission < rightBreakpt.permission; + case BreakpointModel::EnabledColumn: + return leftBreakpt.enabled < rightBreakpt.enabled; + default: + break; + } + + return leftBreakpt.addr < rightBreakpt.addr; +} + +BreakpointWidget::BreakpointWidget(MainWindow *main, QAction *action) : + CutterDockWidget(main, action), + ui(new Ui::BreakpointWidget) +{ + ui->setupUi(this); + + breakpointModel = new BreakpointModel(&breakpoints, this); + breakpointProxyModel = new BreakpointProxyModel(breakpointModel, this); + ui->breakpointTreeView->setModel(breakpointProxyModel); + ui->breakpointTreeView->sortByColumn(BreakpointModel::AddrColumn, Qt::AscendingOrder); + + setScrollMode(); + actionDelBreakpoint = new QAction(tr("Delete breakpoint")); + actionToggleBreakpoint = new QAction(tr("Toggle breakpoint")); + connect(actionDelBreakpoint, &QAction::triggered, this, &BreakpointWidget::delBreakpoint); + connect(actionToggleBreakpoint, &QAction::triggered, this, &BreakpointWidget::toggleBreakpoint); + connect(Core(), &CutterCore::refreshAll, this, &BreakpointWidget::refreshBreakpoint); + connect(Core(), &CutterCore::breakpointsChanged, this, &BreakpointWidget::refreshBreakpoint); + connect(ui->delAllBreakpoints, &QAbstractButton::clicked, Core(), &CutterCore::delAllBreakpoints); + ui->breakpointTreeView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->breakpointTreeView, SIGNAL(customContextMenuRequested(const QPoint &)), + this, SLOT(showBreakpointContextMenu(const QPoint &))); +} + +BreakpointWidget::~BreakpointWidget() {} + +void BreakpointWidget::refreshBreakpoint() +{ + breakpointModel->beginReloadBreakpoint(); + breakpoints = Core()->getBreakpoints(); + breakpointModel->endReloadBreakpoint(); + + ui->breakpointTreeView->resizeColumnToContents(0); + ui->breakpointTreeView->resizeColumnToContents(1); + ui->breakpointTreeView->resizeColumnToContents(2); +} + +void BreakpointWidget::setScrollMode() +{ + qhelpers::setVerticalScrollMode(ui->breakpointTreeView); +} + +void BreakpointWidget::on_breakpointTreeView_doubleClicked(const QModelIndex &index) +{ + BreakpointDescription item = index.data(BreakpointModel::BreakpointDescriptionRole).value(); + Core()->seek(item.addr); +} + +void BreakpointWidget::showBreakpointContextMenu(const QPoint &pt) +{ + QMenu *menu = new QMenu(ui->breakpointTreeView); + menu->clear(); + menu->addAction(actionDelBreakpoint); + menu->addAction(actionToggleBreakpoint); + + menu->exec(ui->breakpointTreeView->mapToGlobal(pt)); + this->setContextMenuPolicy(Qt::CustomContextMenu); + + delete menu; +} + +void BreakpointWidget::delBreakpoint() +{ + BreakpointDescription bp = ui->breakpointTreeView->selectionModel()->currentIndex().data( + BreakpointModel::BreakpointDescriptionRole).value(); + Core()->delBreakpoint(bp.addr); +} + +void BreakpointWidget::toggleBreakpoint() +{ + BreakpointDescription bp = ui->breakpointTreeView->selectionModel()->currentIndex().data( + BreakpointModel::BreakpointDescriptionRole).value(); + if (bp.enabled) { + Core()->disableBreakpoint(bp.addr); + } else { + Core()->enableBreakpoint(bp.addr); + } +} \ No newline at end of file diff --git a/src/widgets/BreakpointWidget.h b/src/widgets/BreakpointWidget.h new file mode 100644 index 00000000..2cbdc277 --- /dev/null +++ b/src/widgets/BreakpointWidget.h @@ -0,0 +1,89 @@ +#pragma once + +#include + +#include "Cutter.h" +#include "CutterDockWidget.h" + +#include +#include + +class MainWindow; +class QTreeWidget; + +namespace Ui +{ + class BreakpointWidget; +} + + +class MainWindow; +class QTreeWidgetItem; + + +class BreakpointModel: public QAbstractListModel +{ + Q_OBJECT + +private: + QList *breakpoints; + +public: + enum Column { AddrColumn = 0, PermColumn, HwColumn, TraceColumn, EnabledColumn, ColumnCount }; + enum Role { BreakpointDescriptionRole = Qt::UserRole }; + + BreakpointModel(QList *breakpoints, QObject *parent = 0); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + void beginReloadBreakpoint(); + void endReloadBreakpoint(); +}; + + + +class BreakpointProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + BreakpointProxyModel(BreakpointModel *sourceModel, QObject *parent = nullptr); + +protected: + bool filterAcceptsRow(int row, const QModelIndex &parent) const override; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; +}; + + + +class BreakpointWidget : public CutterDockWidget +{ + Q_OBJECT + +public: + explicit BreakpointWidget(MainWindow *main, QAction *action = nullptr); + ~BreakpointWidget(); + +private slots: + void on_breakpointTreeView_doubleClicked(const QModelIndex &index); + void showBreakpointContextMenu(const QPoint &pt); + void delBreakpoint(); + void toggleBreakpoint(); + + void refreshBreakpoint(); + +private: + std::unique_ptr ui; + + BreakpointModel *breakpointModel; + BreakpointProxyModel *breakpointProxyModel; + QList breakpoints; + QAction *actionDelBreakpoint = nullptr; + QAction *actionToggleBreakpoint = nullptr; + + void setScrollMode(); +}; diff --git a/src/widgets/BreakpointWidget.ui b/src/widgets/BreakpointWidget.ui new file mode 100644 index 00000000..842d1a6f --- /dev/null +++ b/src/widgets/BreakpointWidget.ui @@ -0,0 +1,65 @@ + + + BreakpointWidget + + + + 0 + 0 + 400 + 300 + + + + Breakpoints + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QTreeView::item +{ + padding-top: 1px; + padding-bottom: 1px; +} + + + QFrame::NoFrame + + + 0 + + + 8 + + + true + + + + + + + Delete all breakpoints + + + + + + + + +