diff --git a/docs/source/shortcuts.rst b/docs/source/shortcuts.rst index bdc14c16..52ca1dd8 100644 --- a/docs/source/shortcuts.rst +++ b/docs/source/shortcuts.rst @@ -104,16 +104,18 @@ Graph view shortcuts Debug shortcuts --------------- -+-----------------+----------------+ -| Shortcut | Function | -+=================+================+ -| F9 | Start debug | -+-----------------+----------------+ -| F7 | Step into | -+-----------------+----------------+ -| F8 | Step over | -+-----------------+----------------+ -| F5 | Continue | -+-----------------+----------------+ -| F2/(Ctrl/Cmd)+B | Add breakpoint | -+-----------------+----------------+ ++-----------------+------------------------------------------+ +| Shortcut | Function | ++=================+==========================================+ +| F9 | Start debug | ++-----------------+------------------------------------------+ +| F7 | Step into | ++-----------------+------------------------------------------+ +| F8 | Step over | ++-----------------+------------------------------------------+ +| F5 | Continue | ++-----------------+------------------------------------------+ +| F2/(Ctrl/Cmd)+B | Add or Remove breakpoint | ++-----------------+------------------------------------------+ +| (Ctrl/Cmd)+F2 | Edit or open Advanced breakpoint dialog | ++-----------------+------------------------------------------+ diff --git a/src/common/Helpers.cpp b/src/common/Helpers.cpp index 655ae6c6..94daed0a 100644 --- a/src/common/Helpers.cpp +++ b/src/common/Helpers.cpp @@ -13,6 +13,7 @@ #include #include #include +#include static QAbstractItemView::ScrollMode scrollMode() { @@ -257,5 +258,16 @@ qreal devicePixelRatio(const QPaintDevice *p) #endif } +void selectIndexByData(QComboBox *widget, QVariant data, int defaultIndex) +{ + for (int i = 0; i < widget->count(); i++) { + if (widget->itemData(i) == data) { + widget->setCurrentIndex(i); + return; + } + } + widget->setCurrentIndex(defaultIndex); +} + } // end namespace diff --git a/src/common/Helpers.h b/src/common/Helpers.h index d7c60419..50c52972 100644 --- a/src/common/Helpers.h +++ b/src/common/Helpers.h @@ -19,6 +19,7 @@ class QTreeView; class QAction; class QMenu; class QPaintDevice; +class QComboBox; namespace qhelpers { QString formatBytecount(const uint64_t bytecount); @@ -55,6 +56,13 @@ void setThemeIcons(QList> supportedIconsNames, std::functi void prependQAction(QAction *action, QMenu *menu); qreal devicePixelRatio(const QPaintDevice *p); +/** + * @brief Select comboBox item by value in Qt::UserRole. + * @param comboBox + * @param data - value to search in combobox item data + * @param defaultIndex - item to select in case no match + */ +void selectIndexByData(QComboBox *comboBox, QVariant data, int defaultIndex = -1); } // qhelpers diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 822e28a3..8c23443a 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -104,6 +104,13 @@ namespace RJsonKey { #undef R_JSON_KEY +static void updateOwnedCharPtr(char *&variable, const QString &newValue) +{ + auto data = newValue.toUtf8(); + R_FREE(variable) + variable = strdup(data.data()); +} + RCoreLocked::RCoreLocked(CutterCore *core) : core(core) { @@ -457,7 +464,7 @@ QStringList CutterCore::autocomplete(const QString &cmd, RLinePromptType promptT QStringList r; r.reserve(r_pvector_len(&completion.args)); - for (size_t i = 0; i< r_pvector_len(&completion.args); i++) { + for (size_t i = 0; i < r_pvector_len(&completion.args); i++) { r.push_back(QString::fromUtf8(reinterpret_cast(r_pvector_at(&completion.args, i)))); } @@ -1468,7 +1475,7 @@ void CutterCore::attachDebug(int pid) void CutterCore::suspendDebug() { - debugTask->breakTask(); + debugTask->breakTask(); } void CutterCore::stopDebug() @@ -1749,6 +1756,73 @@ void CutterCore::addBreakpoint(QString addr) emit breakpointsChanged(); } +void CutterCore::addBreakpoint(const BreakpointDescription &config) +{ + CORE_LOCK(); + RBreakpointItem *breakpoint = nullptr; + int watchpoint_prot = 0; + if (config.hw) { + watchpoint_prot = config.permission & ~(R_BP_PROT_EXEC); + } + + auto address = config.addr; + char *module = nullptr; + QByteArray moduleNameData; + if (config.type == BreakpointDescription::Named) { + address = Core()->math(config.positionExpression); + } else if (config.type == BreakpointDescription::Module) { + address = 0; + moduleNameData = config.positionExpression.toUtf8(); + module = moduleNameData.data(); + } + breakpoint = r_debug_bp_add(core->dbg, address, (config.hw && watchpoint_prot == 0), + watchpoint_prot, watchpoint_prot, + module, config.moduleDelta); + if (config.type == BreakpointDescription::Named) { + updateOwnedCharPtr(breakpoint->expr, config.positionExpression); + } + + if (config.hw) { + breakpoint->size = config.size; + } + if (config.type == BreakpointDescription::Named) { + updateOwnedCharPtr(breakpoint->name, config.positionExpression); + } + + if (!breakpoint) { + QMessageBox::critical(nullptr, tr("Breakpoint error"), tr("Failed to create breakpoint")); + return; + } + int index = std::find(core->dbg->bp->bps_idx, + core->dbg->bp->bps_idx + core->dbg->bp->bps_idx_count, + breakpoint) - core->dbg->bp->bps_idx; + + breakpoint->enabled = config.enabled; + if (config.trace) { + setBreakpointTrace(index, config.trace); + } + if (!config.condition.isEmpty()) { + updateOwnedCharPtr(breakpoint->cond, config.condition); + } + if (!config.command.isEmpty()) { + updateOwnedCharPtr(breakpoint->data, config.command); + } + emit instructionChanged(breakpoint->addr); + emit breakpointsChanged(); +} + +void CutterCore::updateBreakpoint(int index, const BreakpointDescription &config) +{ + CORE_LOCK(); + if (auto bp = r_bp_get_index(core->dbg->bp, index)) { + r_bp_del(core->dbg->bp, bp->addr); + } + // Delete by index currently buggy, + // required for breakpoints with non address based position + //r_bp_del_index(core->dbg->bp, index); + addBreakpoint(config); +} + void CutterCore::delBreakpoint(RVA addr) { cmd("db- " + RAddressString(addr)); @@ -1785,24 +1859,52 @@ void CutterCore::setBreakpointTrace(int index, bool enabled) } } +static BreakpointDescription breakpointDescriptionFromR2(int index, r_bp_item_t *bpi) +{ + BreakpointDescription bp; + bp.addr = bpi->addr; + bp.index = index; + bp.size = bpi->size; + if (bpi->expr) { + bp.positionExpression = bpi->expr; + bp.type = BreakpointDescription::Named; + } + bp.name = bpi->name; + bp.permission = bpi->perm; + bp.command = bpi->data; + bp.condition = bpi->cond; + bp.hw = bpi->hw; + bp.trace = bpi->trace; + bp.enabled = bpi->enabled; + return bp; +} + +int CutterCore::breakpointIndexAt(RVA addr) +{ + CORE_LOCK(); + return r_bp_get_index_at(core->dbg->bp, addr); +} + +BreakpointDescription CutterCore::getBreakpointAt(RVA addr) +{ + CORE_LOCK(); + int index = breakpointIndexAt(addr); + auto bp = r_bp_get_index(core->dbg->bp, index); + if (bp) { + return breakpointDescriptionFromR2(index, bp); + } + return BreakpointDescription(); +} + QList CutterCore::getBreakpoints() { + CORE_LOCK(); QList ret; - QJsonArray breakpointArray = cmdj("dbj").array(); - - for (const QJsonValue &value : breakpointArray) { - QJsonObject bpObject = value.toObject(); - - BreakpointDescription bp; - - bp.addr = bpObject[RJsonKey::addr].toVariant().toULongLong(); - bp.size = bpObject[RJsonKey::size].toInt(); - bp.permission = bpObject[RJsonKey::prot].toString(); - bp.hw = bpObject[RJsonKey::hw].toBool(); - bp.trace = bpObject[RJsonKey::trace].toBool(); - bp.enabled = bpObject[RJsonKey::enabled].toBool(); - - ret << bp; + //TODO: use higher level API, don't touch r2 bps_idx directly + for (int i = 0; i < core->dbg->bp->bps_idx_count; i++) { + if (auto bpi = core->dbg->bp->bps_idx[i]) { + ret.push_back(breakpointDescriptionFromR2(i, bpi)); + } } return ret; diff --git a/src/core/Cutter.h b/src/core/Cutter.h index d7840a9e..0660f374 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -319,7 +319,10 @@ public: void stepDebug(); void stepOverDebug(); void stepOutDebug(); + void addBreakpoint(QString addr); + void addBreakpoint(const BreakpointDescription &config); + void updateBreakpoint(int index, const BreakpointDescription &config); void toggleBreakpoint(RVA addr); void toggleBreakpoint(QString addr); void delBreakpoint(RVA addr); @@ -332,6 +335,8 @@ public: * @param enabled - true if tracing should be enabled */ void setBreakpointTrace(int index, bool enabled); + int breakpointIndexAt(RVA addr); + BreakpointDescription getBreakpointAt(RVA addr); bool isBreakpoint(const QList &breakpoints, RVA addr); QList getBreakpointsAddresses(); diff --git a/src/core/CutterDescriptions.h b/src/core/CutterDescriptions.h index 994738b3..59d04ece 100644 --- a/src/core/CutterDescriptions.h +++ b/src/core/CutterDescriptions.h @@ -276,12 +276,25 @@ struct MemoryMapDescription { }; struct BreakpointDescription { - RVA addr; - int size; - QString permission; - bool hw; - bool trace; - bool enabled; + enum PositionType { + Address, + Named, + Module, + }; + + RVA addr = 0; + int64_t moduleDelta = 0; + int index = -1; + PositionType type = Address; + int size = 0; + int permission = 0; + QString positionExpression; + QString name; + QString command; + QString condition; + bool hw = false; + bool trace = false; + bool enabled = true; }; struct ProcessDescription { @@ -338,6 +351,7 @@ Q_DECLARE_METATYPE(SectionDescription) Q_DECLARE_METATYPE(SegmentDescription) Q_DECLARE_METATYPE(MemoryMapDescription) Q_DECLARE_METATYPE(BreakpointDescription) +Q_DECLARE_METATYPE(BreakpointDescription::PositionType) Q_DECLARE_METATYPE(ProcessDescription) Q_DECLARE_METATYPE(RegisterRefDescription) Q_DECLARE_METATYPE(VariableDescription) diff --git a/src/dialogs/BreakpointsDialog.cpp b/src/dialogs/BreakpointsDialog.cpp index a340dda4..28a50393 100644 --- a/src/dialogs/BreakpointsDialog.cpp +++ b/src/dialogs/BreakpointsDialog.cpp @@ -1,47 +1,213 @@ #include "BreakpointsDialog.h" #include "ui_BreakpointsDialog.h" +#include "Cutter.h" +#include "Helpers.h" -BreakpointsDialog::BreakpointsDialog(QWidget *parent) : +#include +#include +#include + +BreakpointsDialog::BreakpointsDialog(bool editMode, QWidget *parent) : QDialog(parent), - ui(new Ui::BreakpointsDialog) + ui(new Ui::BreakpointsDialog), + editMode(editMode) { ui->setupUi(this); setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); - // Event filter for capturing Ctrl/Cmd+Return - ui->textEdit->installEventFilter(this); + connect(ui->breakpointPosition, &QLineEdit::textChanged, this, &BreakpointsDialog::refreshOkButton); + refreshOkButton(); + + if (editMode) { + setWindowTitle(tr("Edit breakpoint")); + } else { + setWindowTitle(tr("New breakpoint")); + } + + + struct { + QString label; + QString tooltip; + BreakpointDescription::PositionType type; + } positionTypes[] = { + {tr("Address"), tr("Address or expression calculated when creating breakpoint"), BreakpointDescription::Address}, + {tr("Named"), tr("Expression - stored as expression"), BreakpointDescription::Named}, + {tr("Module offset"), tr("Offset relative to module"), BreakpointDescription::Module}, + }; + int index = 0; + for (auto &item : positionTypes) { + ui->positionType->addItem(item.label, item.type); + ui->positionType->setItemData(index, item.tooltip, Qt::ToolTipRole); + index++; + } + + connect(ui->positionType, static_cast(&QComboBox::currentIndexChanged), + this, &BreakpointsDialog::onTypeChanged); + onTypeChanged(); + + auto modules = Core()->getMemoryMap(); + QSet moduleNames; + for (const auto &module : modules) { + moduleNames.insert(module.fileName); + } + for (const auto& module : moduleNames) { + ui->moduleName->addItem(module); + } + ui->moduleName->setCurrentText(""); + // Suggest completion when user tries to enter file name not only full path + ui->moduleName->completer()->setFilterMode(Qt::MatchContains); + + ui->breakpointCondition->setCompleter(nullptr); // Don't use examples for completing + configureCheckboxRestrictions(); +} + +BreakpointsDialog::BreakpointsDialog(const BreakpointDescription &breakpoint, QWidget *parent) + : BreakpointsDialog(true, parent) +{ + switch (breakpoint.type) { + case BreakpointDescription::Address: + ui->breakpointPosition->setText(RAddressString(breakpoint.addr)); + break; + case BreakpointDescription::Named: + ui->breakpointPosition->setText(breakpoint.positionExpression); + break; + case BreakpointDescription::Module: + ui->breakpointPosition->setText(QString::number(breakpoint.moduleDelta)); + ui->moduleName->setCurrentText(breakpoint.positionExpression); + break; + } + for (int i = 0; i < ui->positionType->count(); i++) { + if (ui->positionType->itemData(i) == breakpoint.type) { + ui->positionType->setCurrentIndex(i); + } + } + ui->breakpointCommand->setPlainText(breakpoint.command); + ui->breakpointCondition->setEditText(breakpoint.condition); + if (breakpoint.hw) { + ui->radioHardware->setChecked(true); + ui->hwRead->setChecked(breakpoint.permission & R_BP_PROT_READ); + ui->hwWrite->setChecked(breakpoint.permission & R_BP_PROT_WRITE); + ui->hwExecute->setChecked(breakpoint.permission & R_BP_PROT_EXEC); + ui->breakpointSize->setCurrentText(QString::number(breakpoint.size)); + } else { + ui->radioSoftware->setChecked(true); + } + ui->checkTrace->setChecked(breakpoint.trace); + ui->checkEnabled->setChecked(breakpoint.enabled); + refreshOkButton(); +} + +BreakpointsDialog::BreakpointsDialog(RVA address, QWidget *parent) + : BreakpointsDialog(false, parent) +{ + if (address != RVA_INVALID) { + ui->breakpointPosition->setText(RAddressString(address)); + } + refreshOkButton(); } BreakpointsDialog::~BreakpointsDialog() {} -void BreakpointsDialog::on_buttonBox_accepted() +BreakpointDescription BreakpointsDialog::getDescription() { -} - -void BreakpointsDialog::on_buttonBox_rejected() -{ - close(); -} - -QString BreakpointsDialog::getBreakpoints() -{ - QString ret = ui->textEdit->document()->toPlainText(); - return ret; -} - -bool BreakpointsDialog::eventFilter(QObject *obj, QEvent *event) -{ - Q_UNUSED(obj); - if (event -> type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast (event); - - // Confirm comment by pressing Ctrl/Cmd+Return - if ((keyEvent -> modifiers() & Qt::ControlModifier) && - ((keyEvent -> key() == Qt::Key_Enter) || (keyEvent -> key() == Qt::Key_Return))) { - this->accept(); - return true; - } + BreakpointDescription breakpoint; + auto positionType = ui->positionType->currentData().value(); + switch (positionType) { + case BreakpointDescription::Address: + breakpoint.addr = Core()->math(ui->breakpointPosition->text()); + break; + case BreakpointDescription::Named: + breakpoint.positionExpression = ui->breakpointPosition->text().trimmed(); + break; + case BreakpointDescription::Module: + breakpoint.moduleDelta = static_cast(Core()->math(ui->breakpointPosition->text())); + breakpoint.positionExpression = ui->moduleName->currentText().trimmed(); + break; } + breakpoint.type = positionType; - return false; + breakpoint.size = Core()->num(ui->breakpointSize->currentText()); + breakpoint.condition = ui->breakpointCondition->currentText().trimmed(); + breakpoint.command = ui->breakpointCommand->toPlainText().trimmed(); + if (ui->radioHardware->isChecked()) { + breakpoint.hw = true; + breakpoint.permission = getHwPermissions(); + } else { + breakpoint.hw = false; + } + breakpoint.trace = ui->checkTrace->isChecked(); + breakpoint.enabled = ui->checkEnabled->isChecked(); + return breakpoint; +} + +void BreakpointsDialog::createNewBreakpoint(RVA address, QWidget *parent) +{ + BreakpointsDialog editDialog(address, parent); + if (editDialog.exec() == QDialog::Accepted) { + Core()->addBreakpoint(editDialog.getDescription()); + } +} + +void BreakpointsDialog::editBreakpoint(const BreakpointDescription &breakpoint, QWidget *parent) +{ + BreakpointsDialog editDialog(breakpoint, parent); + if (editDialog.exec() == QDialog::Accepted) { + Core()->updateBreakpoint(breakpoint.index, editDialog.getDescription()); + } +} + +void BreakpointsDialog::refreshOkButton() +{ + auto button = ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok); + button->setDisabled(ui->breakpointPosition->text().isEmpty()); +} + +void BreakpointsDialog::onTypeChanged() +{ + bool moduleEnabled = ui->positionType->currentData() == QVariant(BreakpointDescription::Module); + ui->moduleLabel->setEnabled(moduleEnabled); + ui->moduleName->setEnabled(moduleEnabled); + ui->breakpointPosition->setPlaceholderText(ui->positionType->currentData(Qt::ToolTipRole).toString()); +} + +void BreakpointsDialog::configureCheckboxRestrictions() +{ + auto atLeastOneChecked = [this]() { + if (this->getHwPermissions() == 0) { + this->ui->hwExecute->setChecked(true); + } + }; + auto rwRule = [this, atLeastOneChecked](bool checked) { + if (checked) { + this->ui->hwExecute->setChecked(false); + } else { + atLeastOneChecked(); + } + }; + connect(ui->hwRead, &QCheckBox::toggled, this, rwRule); + connect(ui->hwWrite, &QCheckBox::toggled, this, rwRule); + auto execRule = [this, atLeastOneChecked](bool checked) { + if (checked) { + this->ui->hwRead->setChecked(false); + this->ui->hwWrite->setChecked(false); + } else { + atLeastOneChecked(); + } + }; + connect(ui->hwExecute, &QCheckBox::toggled, this, execRule); +} + +int BreakpointsDialog::getHwPermissions() +{ + int result = 0; + if (ui->hwRead->isChecked()) { + result |= R_BP_PROT_READ; + } + if (ui->hwWrite->isChecked()) { + result |= R_BP_PROT_WRITE; + } + if (ui->hwExecute->isChecked()) { + result |= R_BP_PROT_EXEC; + } + return result; } diff --git a/src/dialogs/BreakpointsDialog.h b/src/dialogs/BreakpointsDialog.h index 216bf2bc..2c3e6d64 100644 --- a/src/dialogs/BreakpointsDialog.h +++ b/src/dialogs/BreakpointsDialog.h @@ -2,6 +2,7 @@ #include #include +#include "CutterDescriptions.h" namespace Ui { class BreakpointsDialog; @@ -12,17 +13,21 @@ class BreakpointsDialog : public QDialog Q_OBJECT public: - explicit BreakpointsDialog(QWidget *parent = nullptr); + explicit BreakpointsDialog(bool editMode = false, QWidget *parent = nullptr); + BreakpointsDialog(const BreakpointDescription &editableBreakpoint, QWidget *parent = nullptr); + BreakpointsDialog(RVA address, QWidget *parent = nullptr); ~BreakpointsDialog(); - QString getBreakpoints(); - -private slots: - void on_buttonBox_accepted(); - void on_buttonBox_rejected(); + BreakpointDescription getDescription(); + static void createNewBreakpoint(RVA address = RVA_INVALID, QWidget *parent = nullptr); + static void editBreakpoint(const BreakpointDescription& breakpoint, QWidget *parent = nullptr); private: std::unique_ptr ui; + bool editMode = false; - bool eventFilter(QObject *obj, QEvent *event); + void refreshOkButton(); + void onTypeChanged(); + void configureCheckboxRestrictions(); + int getHwPermissions(); }; diff --git a/src/dialogs/BreakpointsDialog.ui b/src/dialogs/BreakpointsDialog.ui index db4f3632..7ab48a2f 100644 --- a/src/dialogs/BreakpointsDialog.ui +++ b/src/dialogs/BreakpointsDialog.ui @@ -6,36 +6,287 @@ 0 0 - 400 - 118 + 610 + 437 - Add breakpoints + Add/Edit breakpoint - - - 2 - - - 2 - - - 5 - - - 2 - - - 2 - + + + + + + + Position + + + breakpointPosition + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + Condition + + + breakpointCondition + + + + + + + + 0 + 0 + + + + true + + + + + + -1 + + + QComboBox::NoInsert + + + true + + + + ?v $.rax-0x6 # break when rax is 6 + + + + + + + + Module + + + moduleName + + + + + + + + 0 + 0 + + + + true + + + + + - - 0 - - + + + Type/Options + + + + + + Enabled + + + true + + + + + + + Software + + + true + + + + + + + Hardware + + + + + + + false + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Read + + + + + + + Write + + + + + + + Execute + + + true + + + + + + + + + Size + + + breakpointSize + + + + + + + + 1 + + + + + 2 + + + + + 4 + + + + + 8 + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + Action + + + + + + Trace + + + + + + + + + Command + + + breakpointCommand + + + + + + + + + + @@ -51,6 +302,17 @@ + + positionType + breakpointPosition + moduleName + breakpointCondition + checkEnabled + radioSoftware + radioHardware + checkTrace + breakpointCommand + @@ -60,8 +322,8 @@ accept() - 248 - 254 + 260 + 450 157 @@ -76,8 +338,8 @@ reject() - 316 - 260 + 328 + 450 286 @@ -85,5 +347,21 @@ + + radioHardware + toggled(bool) + hwConfigBox + setEnabled(bool) + + + 77 + 246 + + + 77 + 320 + + + diff --git a/src/menus/DisassemblyContextMenu.cpp b/src/menus/DisassemblyContextMenu.cpp index f34a6dea..9aeb57fb 100644 --- a/src/menus/DisassemblyContextMenu.cpp +++ b/src/menus/DisassemblyContextMenu.cpp @@ -10,6 +10,7 @@ #include "dialogs/EditFunctionDialog.h" #include "dialogs/LinkTypeDialog.h" #include "dialogs/EditStringDialog.h" +#include "dialogs/BreakpointsDialog.h" #include "MainWindow.h" #include @@ -56,6 +57,7 @@ DisassemblyContextMenu::DisassemblyContextMenu(QWidget *parent, MainWindow *main actionSetBits64(this), actionContinueUntil(this), actionAddBreakpoint(this), + actionAdvancedBreakpoint(this), actionSetPC(this), actionSetToCode(this), actionSetAsStringAuto(this), @@ -292,6 +294,9 @@ void DisassemblyContextMenu::addDebugMenu() initAction(&actionAddBreakpoint, tr("Add/remove breakpoint"), SLOT(on_actionAddBreakpoint_triggered()), getAddBPSequence()); debugMenu->addAction(&actionAddBreakpoint); + initAction(&actionAdvancedBreakpoint, tr("Advanced breakpoint"), + SLOT(on_actionAdvancedBreakpoint_triggered()), QKeySequence(Qt::CTRL+Qt::Key_F2)); + debugMenu->addAction(&actionAdvancedBreakpoint); initAction(&actionContinueUntil, tr("Continue until line"), SLOT(on_actionContinueUntil_triggered())); @@ -503,9 +508,13 @@ void DisassemblyContextMenu::aboutToShowSlot() // Only show debug options if we are currently debugging debugMenu->menuAction()->setVisible(Core()->currentlyDebugging); + bool hasBreakpoint = Core()->breakpointIndexAt(offset) > -1; + actionAddBreakpoint.setText(hasBreakpoint ? + tr("Remove breakpoint") : tr("Add breakpoint")); + actionAdvancedBreakpoint.setText(hasBreakpoint ? + tr("Edit breakpoint") : tr("Advanced breakpoint")); QString progCounterName = Core()->getRegisterName("PC").toUpper(); actionSetPC.setText("Set " + progCounterName + " here"); - } QKeySequence DisassemblyContextMenu::getCopySequence() const @@ -731,6 +740,16 @@ void DisassemblyContextMenu::on_actionAddBreakpoint_triggered() Core()->toggleBreakpoint(offset); } +void DisassemblyContextMenu::on_actionAdvancedBreakpoint_triggered() +{ + int index = Core()->breakpointIndexAt(offset); + if (index >= 0) { + BreakpointsDialog::editBreakpoint(Core()->getBreakpointAt(offset), this); + } else { + BreakpointsDialog::createNewBreakpoint(offset, this); + } +} + void DisassemblyContextMenu::on_actionContinueUntil_triggered() { Core()->continueUntilDebug(RAddressString(offset)); diff --git a/src/menus/DisassemblyContextMenu.h b/src/menus/DisassemblyContextMenu.h index e03d96c8..081d2341 100644 --- a/src/menus/DisassemblyContextMenu.h +++ b/src/menus/DisassemblyContextMenu.h @@ -53,6 +53,7 @@ private slots: void on_actionDeleteFunction_triggered(); void on_actionAddBreakpoint_triggered(); + void on_actionAdvancedBreakpoint_triggered(); void on_actionContinueUntil_triggered(); void on_actionSetPC_triggered(); @@ -157,6 +158,7 @@ private: QMenu *debugMenu; QAction actionContinueUntil; QAction actionAddBreakpoint; + QAction actionAdvancedBreakpoint; QAction actionSetPC; QAction actionSetToCode; diff --git a/src/widgets/BreakpointWidget.cpp b/src/widgets/BreakpointWidget.cpp index 24c86d05..41f13085 100644 --- a/src/widgets/BreakpointWidget.cpp +++ b/src/widgets/BreakpointWidget.cpp @@ -30,6 +30,20 @@ int BreakpointModel::columnCount(const QModelIndex &) const return BreakpointModel::ColumnCount; } +static QString formatHwBreakpoint(int permission) { + char data[] = "rwx"; + if ((permission & (R_BP_PROT_READ | R_BP_PROT_ACCESS)) == 0) { + data[0] = '-'; + } + if ((permission & (R_BP_PROT_WRITE | R_BP_PROT_ACCESS)) == 0) { + data[1] = '-'; + } + if ((permission & R_BP_PROT_EXEC) == 0) { + data[2] = '-'; + } + return data; +} + QVariant BreakpointModel::data(const QModelIndex &index, int role) const { if (index.row() >= breakpoints.count()) @@ -42,10 +56,15 @@ QVariant BreakpointModel::data(const QModelIndex &index, int role) const switch (index.column()) { case AddrColumn: return RAddressString(breakpoint.addr); - case PermColumn: - return breakpoint.permission; - case HwColumn: - return breakpoint.hw; + case NameColumn: + return breakpoint.name; + case TypeColumn: + + if (breakpoint.hw) { + return tr("HW %1").arg(formatHwBreakpoint(breakpoint.permission)); + } else { + return tr("SW"); + } case TraceColumn: return breakpoint.trace; case EnabledColumn: @@ -55,6 +74,8 @@ QVariant BreakpointModel::data(const QModelIndex &index, int role) const } case Qt::EditRole: switch (index.column()) { + case AddrColumn: + return breakpoint.addr; case TraceColumn: return breakpoint.trace; case EnabledColumn: @@ -76,14 +97,14 @@ QVariant BreakpointModel::headerData(int section, Qt::Orientation, int role) con switch (section) { case AddrColumn: return tr("Offset"); - case PermColumn: - return tr("Permissions"); - case HwColumn: - return tr("Hardware bp"); + case NameColumn: + return tr("Name"); + case TypeColumn: + return tr("Type"); case TraceColumn: return tr("Tracing"); case EnabledColumn: - return tr("Active"); + return tr("Enabled"); default: return QVariant(); } @@ -104,7 +125,7 @@ bool BreakpointModel::setData(const QModelIndex &index, const QVariant &value, i switch (index.column()) { case TraceColumn: breakpoint.trace = value.toBool(); - Core()->setBreakpointTrace(index.row(), breakpoint.trace); + Core()->setBreakpointTrace(breakpoint.index, breakpoint.trace); emit dataChanged(index, index, {role, Qt::DisplayRole}); return true; case EnabledColumn: @@ -148,37 +169,8 @@ RVA BreakpointModel::address(const QModelIndex &index) const BreakpointProxyModel::BreakpointProxyModel(BreakpointModel *sourceModel, QObject *parent) : AddressableFilterProxyModel(sourceModel, parent) { -} - -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; + // Use numeric values instead of numbers converted to strings if available + this->setSortRole(Qt::EditRole); } BreakpointWidget::BreakpointWidget(MainWindow *main, QAction *action) : @@ -212,7 +204,11 @@ BreakpointWidget::BreakpointWidget(MainWindow *main, QAction *action) : connect(actionToggleBreakpoint, &QAction::triggered, this, &BreakpointWidget::toggleBreakpoint); ui->breakpointTreeView->addAction(actionToggleBreakpoint); + actionEditBreakpoint = new QAction(tr("Edit"), this); + connect(actionEditBreakpoint, &QAction::triggered, this, &BreakpointWidget::editBreakpoint); + auto contextMenu = ui->breakpointTreeView->getItemContextMenu(); + contextMenu->addAction(actionEditBreakpoint); contextMenu->addAction(actionToggleBreakpoint); contextMenu->addAction(actionDelBreakpoint); @@ -247,17 +243,7 @@ void BreakpointWidget::setScrollMode() void BreakpointWidget::addBreakpointDialog() { - BreakpointsDialog dialog(this); - - if (dialog.exec()) { - QString bps = dialog.getBreakpoints(); - if (!bps.isEmpty()) { - QStringList bpList = bps.split(QLatin1Char(' '), QString::SkipEmptyParts); - for (const QString &bp : bpList) { - Core()->addBreakpoint(bp); - } - } - } + BreakpointsDialog::createNewBreakpoint(RVA_INVALID, this); } QVector BreakpointWidget::getSelectedAddresses() const @@ -289,3 +275,15 @@ void BreakpointWidget::toggleBreakpoint() } editing = false; } + +void BreakpointWidget::editBreakpoint() +{ + auto index = ui->breakpointTreeView->currentIndex(); + if (index.isValid()) { + auto data = breakpointProxyModel->data(index, BreakpointModel::BreakpointDescriptionRole); + if (!data.isNull()) { + auto breakpoint = data.value(); + BreakpointsDialog::editBreakpoint(breakpoint, this); + } + } +} diff --git a/src/widgets/BreakpointWidget.h b/src/widgets/BreakpointWidget.h index 3068dc5b..51bba9b6 100644 --- a/src/widgets/BreakpointWidget.h +++ b/src/widgets/BreakpointWidget.h @@ -31,7 +31,7 @@ private: QList breakpoints; public: - enum Column { AddrColumn = 0, PermColumn, HwColumn, TraceColumn, EnabledColumn, ColumnCount }; + enum Column { AddrColumn = 0, NameColumn, TypeColumn, TraceColumn, EnabledColumn, ColumnCount }; enum Role { BreakpointDescriptionRole = Qt::UserRole }; BreakpointModel(QObject *parent = nullptr); @@ -60,9 +60,6 @@ class BreakpointProxyModel : public AddressableFilterProxyModel 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; }; @@ -78,6 +75,7 @@ public: private slots: void delBreakpoint(); void toggleBreakpoint(); + void editBreakpoint(); void addBreakpointDialog(); void refreshBreakpoint(); @@ -89,6 +87,7 @@ private: QList breakpoints; QAction *actionDelBreakpoint = nullptr; QAction *actionToggleBreakpoint = nullptr; + QAction *actionEditBreakpoint = nullptr; void setScrollMode(); QVector getSelectedAddresses() const;