Breakpoint editor dialog (#1975)

* Add breakpoint dialog for editing breakpoint properties
* Allow editing breakpoint using context menu from breakpointWidget and disassembly menu.
This commit is contained in:
karliss 2020-01-04 20:05:49 +02:00 committed by GitHub
parent 32be76fabc
commit 90c7bfab1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 768 additions and 158 deletions

View File

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

View File

@ -13,6 +13,7 @@
#include <QAbstractButton>
#include <QDockWidget>
#include <QMenu>
#include <QComboBox>
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

View File

@ -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<QPair<void*, QString>> 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

View File

@ -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<const char *>(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<BreakpointDescription> CutterCore::getBreakpoints()
{
CORE_LOCK();
QList<BreakpointDescription> 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;

View File

@ -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<RVA> &breakpoints, RVA addr);
QList<RVA> getBreakpointsAddresses();

View File

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

View File

@ -1,47 +1,213 @@
#include "BreakpointsDialog.h"
#include "ui_BreakpointsDialog.h"
#include "Cutter.h"
#include "Helpers.h"
BreakpointsDialog::BreakpointsDialog(QWidget *parent) :
#include <QPushButton>
#include <QCompleter>
#include <QCheckBox>
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<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this, &BreakpointsDialog::onTypeChanged);
onTypeChanged();
auto modules = Core()->getMemoryMap();
QSet<QString> 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 <QKeyEvent *> (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<BreakpointDescription::PositionType>();
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<int64_t>(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;
}

View File

@ -2,6 +2,7 @@
#include <QDialog>
#include <memory>
#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::BreakpointsDialog> ui;
bool editMode = false;
bool eventFilter(QObject *obj, QEvent *event);
void refreshOkButton();
void onTypeChanged();
void configureCheckboxRestrictions();
int getHwPermissions();
};

View File

@ -6,36 +6,287 @@
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>118</height>
<width>610</width>
<height>437</height>
</rect>
</property>
<property name="windowTitle">
<string>Add breakpoints</string>
<string>Add/Edit breakpoint</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="topGroup">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Position</string>
</property>
<property name="buddy">
<cstring>breakpointPosition</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QWidget" name="widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="positionType"/>
</item>
<item>
<widget class="QLineEdit" name="breakpointPosition"/>
</item>
</layout>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Condition</string>
</property>
<property name="buddy">
<cstring>breakpointCondition</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="breakpointCondition">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>true</bool>
</property>
<property name="currentText">
<string/>
</property>
<property name="currentIndex">
<number>-1</number>
</property>
<property name="insertPolicy">
<enum>QComboBox::NoInsert</enum>
</property>
<property name="frame">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>?v $.rax-0x6 # break when rax is 6</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="moduleLabel">
<property name="text">
<string>Module</string>
</property>
<property name="buddy">
<cstring>moduleName</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="moduleName">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QPlainTextEdit" name="textEdit"/>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Type/Options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="checkEnabled">
<property name="text">
<string>Enabled</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioSoftware">
<property name="text">
<string>Software</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioHardware">
<property name="text">
<string>Hardware</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="hwConfigBox">
<property name="enabled">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QCheckBox" name="hwRead">
<property name="text">
<string>Read</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="hwWrite">
<property name="text">
<string>Write</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="hwExecute">
<property name="text">
<string>Execute</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Size</string>
</property>
<property name="buddy">
<cstring>breakpointSize</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="breakpointSize">
<item>
<property name="text">
<string>1</string>
</property>
</item>
<item>
<property name="text">
<string>2</string>
</property>
</item>
<item>
<property name="text">
<string>4</string>
</property>
</item>
<item>
<property name="text">
<string>8</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Action</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QCheckBox" name="checkTrace">
<property name="text">
<string>Trace</string>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Command</string>
</property>
<property name="buddy">
<cstring>breakpointCommand</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPlainTextEdit" name="breakpointCommand"/>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</item>
@ -51,6 +302,17 @@
</item>
</layout>
</widget>
<tabstops>
<tabstop>positionType</tabstop>
<tabstop>breakpointPosition</tabstop>
<tabstop>moduleName</tabstop>
<tabstop>breakpointCondition</tabstop>
<tabstop>checkEnabled</tabstop>
<tabstop>radioSoftware</tabstop>
<tabstop>radioHardware</tabstop>
<tabstop>checkTrace</tabstop>
<tabstop>breakpointCommand</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
@ -60,8 +322,8 @@
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
<x>260</x>
<y>450</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
@ -76,8 +338,8 @@
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
<x>328</x>
<y>450</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
@ -85,5 +347,21 @@
</hint>
</hints>
</connection>
<connection>
<sender>radioHardware</sender>
<signal>toggled(bool)</signal>
<receiver>hwConfigBox</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>77</x>
<y>246</y>
</hint>
<hint type="destinationlabel">
<x>77</x>
<y>320</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -10,6 +10,7 @@
#include "dialogs/EditFunctionDialog.h"
#include "dialogs/LinkTypeDialog.h"
#include "dialogs/EditStringDialog.h"
#include "dialogs/BreakpointsDialog.h"
#include "MainWindow.h"
#include <QtCore>
@ -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));

View File

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

View File

@ -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<BreakpointDescription>();
return item.permission.contains(filterRegExp());
}
bool BreakpointProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{
BreakpointDescription leftBreakpt = left.data(
BreakpointModel::BreakpointDescriptionRole).value<BreakpointDescription>();
BreakpointDescription rightBreakpt = right.data(
BreakpointModel::BreakpointDescriptionRole).value<BreakpointDescription>();
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<RVA> 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<BreakpointDescription>();
BreakpointsDialog::editBreakpoint(breakpoint, this);
}
}
}

View File

@ -31,7 +31,7 @@ private:
QList<BreakpointDescription> 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<BreakpointDescription> breakpoints;
QAction *actionDelBreakpoint = nullptr;
QAction *actionToggleBreakpoint = nullptr;
QAction *actionEditBreakpoint = nullptr;
void setScrollMode();
QVector<RVA> getSelectedAddresses() const;