diff --git a/include/edb.h b/include/edb.h index 3770dca36..38386309c 100644 --- a/include/edb.h +++ b/include/edb.h @@ -98,6 +98,7 @@ EDB_EXPORT void set_breakpoint_condition(address_t address, const QString &condi EDB_EXPORT void toggle_breakpoint(address_t address); EDB_EXPORT address_t current_data_view_address(); +EDB_EXPORT address_t instruction_pointer_address(); // change what the various views show EDB_EXPORT bool dump_data_range(address_t address, address_t end_address, bool new_tab); diff --git a/plugins/Bookmarks/BookmarkWidget.cpp b/plugins/Bookmarks/BookmarkWidget.cpp index 47fc4b2a7..7b22ecf63 100644 --- a/plugins/Bookmarks/BookmarkWidget.cpp +++ b/plugins/Bookmarks/BookmarkWidget.cpp @@ -20,6 +20,7 @@ along with this program. If not, see . #include "BookmarksModel.h" #include "Expression.h" #include "edb.h" +#include "IBreakpoint.h" #include #include #include @@ -41,9 +42,13 @@ BookmarkWidget::BookmarkWidget(QWidget *parent, Qt::WindowFlags f) ui.tableView->setModel(model_); connect(edb::v1::debugger_ui, SIGNAL(detachEvent()), model_, SLOT(clearBookmarks())); + connect(edb::v1::disassembly_widget(), SIGNAL(signalUpdated()), model_, SLOT(updateList())); connect(ui.buttonAdd, &QPushButton::clicked, this, &BookmarkWidget::buttonAddClicked); connect(ui.buttonDel, &QPushButton::clicked, this, &BookmarkWidget::buttonDelClicked); connect(ui.buttonClear, &QPushButton::clicked, this, &BookmarkWidget::buttonClearClicked); + + toggleBreakpointAction_ = createAction(tr("&Toggle Breakpoint"), QKeySequence(tr("B")), &BookmarkWidget::toggleBreakpoint); + conditionalBreakpointAction_ = createAction(tr("Add &Conditional Breakpoint"), QKeySequence(tr("Shift+B")), &BookmarkWidget::addConditionalBreakpoint); } /** @@ -123,6 +128,44 @@ void BookmarkWidget::buttonClearClicked() { model_->clearBookmarks(); } +/** + * @brief BookmarkWidget::toggleBreakpoint + */ +void BookmarkWidget::toggleBreakpoint() { + + const QItemSelectionModel *const selModel = ui.tableView->selectionModel(); + const QModelIndexList selections = selModel->selectedRows(); + + for (const auto index : selections) { + auto item = static_cast(index.internalPointer()); + edb::v1::toggle_breakpoint(item->address); + } +} + +/** + * @brief BookmarkWidget::addConditionalBreakpoint + */ +void BookmarkWidget::addConditionalBreakpoint() { + + const QItemSelectionModel *const selModel = ui.tableView->selectionModel(); + const QModelIndexList selections = selModel->selectedRows(); + + if (selections.size() == 1) { + bool ok; + const QString condition = QInputDialog::getText(this, tr("Set Breakpoint Condition"), tr("Expression:"), QLineEdit::Normal, QString(), &ok); + auto item = static_cast(selections[0].internalPointer()); + + if (ok) { + if (std::shared_ptr bp = edb::v1::create_breakpoint(item->address)) { + if (!condition.isEmpty()) { + bp->condition = condition; + } + } + } + } +} + + /** * @brief BookmarkWidget::addAddress * @param address @@ -185,6 +228,8 @@ void BookmarkWidget::on_tableView_customContextMenuRequested(const QPoint &pos) menu.addSeparator(); QAction *const actionComment = menu.addAction(tr("&Set Comment")); QAction *const actionType = menu.addAction(tr("Set &Type")); + menu.addAction(toggleBreakpointAction_); + menu.addAction(conditionalBreakpointAction_); QAction *const chosen = menu.exec(ui.tableView->mapToGlobal(pos)); if (chosen == actionAdd) { @@ -230,6 +275,10 @@ void BookmarkWidget::on_tableView_customContextMenuRequested(const QPoint &pos) } } } + } else if (chosen == toggleBreakpointAction_) { + toggleBreakpoint(); + } else if (chosen == conditionalBreakpointAction_) { + addConditionalBreakpoint(); } } @@ -242,4 +291,16 @@ QList BookmarkWidget::entries() const { return bookmarks.toList(); } +// This is copied from Debugger::createAction, so really there should either be a class that implements +// this that both inherit from, or this should be a generic function that takes the QObject* to act upon +// as a parameter. +template +QAction *BookmarkWidget::createAction(const QString &text, const QKeySequence &keySequence, F func) { + auto action = new QAction(text, this); + action->setShortcut(keySequence); + addAction(action); + connect(action, &QAction::triggered, this, func); + return action; +} + } diff --git a/plugins/Bookmarks/BookmarkWidget.h b/plugins/Bookmarks/BookmarkWidget.h index 4834a657c..dac8dd0b2 100644 --- a/plugins/Bookmarks/BookmarkWidget.h +++ b/plugins/Bookmarks/BookmarkWidget.h @@ -50,10 +50,18 @@ public Q_SLOTS: void buttonAddClicked(); void buttonDelClicked(); void buttonClearClicked(); + void toggleBreakpoint(); + void addConditionalBreakpoint(); + +private: + template + QAction *createAction(const QString &text, const QKeySequence &keySequence, F func); private: Ui::BookmarkWidget ui; BookmarksModel *model_ = nullptr; + QAction *toggleBreakpointAction_; + QAction *conditionalBreakpointAction_; }; } diff --git a/plugins/Bookmarks/BookmarksModel.cpp b/plugins/Bookmarks/BookmarksModel.cpp index 04c13ba4b..5aa7bc755 100644 --- a/plugins/Bookmarks/BookmarksModel.cpp +++ b/plugins/Bookmarks/BookmarksModel.cpp @@ -26,7 +26,10 @@ namespace BookmarksPlugin { * @param parent */ BookmarksModel::BookmarksModel(QObject *parent) - : QAbstractItemModel(parent) { + : QAbstractItemModel(parent), + breakpointIcon_(QLatin1String(":/debugger/images/breakpoint.svg")), + currentIcon_(QLatin1String(":/debugger/images/arrow-right.svg")), + currentBpIcon_(QLatin1String(":/debugger/images/arrow-right-red.svg")) { } /** @@ -68,8 +71,17 @@ QVariant BookmarksModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) { switch (index.column()) { - case 0: - return edb::v1::format_pointer(bookmark.address); + case 0: { + // Return the address with symbol name, if there is one + const QString symname = edb::v1::find_function_symbol(bookmark.address); + const QString address = edb::v1::format_pointer(bookmark.address); + + if (!symname.isEmpty()) { + return tr("%1 <%2>").arg(address, symname); + } + + return address; + } case 1: switch (bookmark.type) { case Bookmark::Code: @@ -87,6 +99,36 @@ QVariant BookmarksModel::data(const QModelIndex &index, int role) const { } } + if (role == Qt::DecorationRole) { + switch (index.column()) { + case 0: { + // Puts the correct icon (BP, current BP, current instruction) next to the address + + // TODO: This is mostly copied from QDisassemblyView::drawSidebarElements, and both + // should really be factored out into a common location (although this uses icons + // and the other uses SVG renderers and painting). + const bool is_eip = (bookmark.address == edb::v1::instruction_pointer_address()); + const bool has_breakpoint = (edb::v1::find_breakpoint(bookmark.address) != nullptr); + + const QIcon *icon = nullptr; + if (is_eip) { + icon = has_breakpoint ? ¤tBpIcon_ : ¤tIcon_; + } else if (has_breakpoint) { + icon = &breakpointIcon_; + } + + if (icon) { + return *icon; + } + + // This acts as a dummy icon so even addresses with no icon will be aligned properly + return QColor(0, 0, 0, 0); + } + default: + return QVariant(); + } + } + return QVariant(); } @@ -200,6 +242,20 @@ void BookmarksModel::setType(const QModelIndex &index, const QString &type) { Q_EMIT dataChanged(index, index); } +/** + * @brief BookmarksModel::updateList + */ +void BookmarksModel::updateList() { + // Every time the disassembly view changes, all the bookmark data is invalidated. + // This is not super expensive (unless you have a million bookmarks) but is not + // optimal either. Ideally we could factor out eip updates with the signalUpdated + // signal, and breakpoint updates with the toggleBreakPoint signal, but the latter + // can't use the SIGNAL/SLOT macros which means Debugger.h would need to be moved + // into the include directory. + beginResetModel(); + endResetModel(); +} + /** * @brief BookmarksModel::deleteBookmark * @param index diff --git a/plugins/Bookmarks/BookmarksModel.h b/plugins/Bookmarks/BookmarksModel.h index a7eb27b50..89a537966 100644 --- a/plugins/Bookmarks/BookmarksModel.h +++ b/plugins/Bookmarks/BookmarksModel.h @@ -22,6 +22,7 @@ along with this program. If not, see . #include "Types.h" #include #include +#include namespace BookmarksPlugin { @@ -88,12 +89,16 @@ public Q_SLOTS: void deleteBookmark(const QModelIndex &index); void setComment(const QModelIndex &index, const QString &comment); void setType(const QModelIndex &index, const QString &type); + void updateList(); public: [[nodiscard]] const QVector &bookmarks() const { return bookmarks_; } private: QVector bookmarks_; + QIcon breakpointIcon_; + QIcon currentIcon_; + QIcon currentBpIcon_; }; } diff --git a/src/edb.cpp b/src/edb.cpp index fe7b82890..fbb743e3e 100644 --- a/src/edb.cpp +++ b/src/edb.cpp @@ -1303,6 +1303,20 @@ address_t current_data_view_address() { return qobject_cast(ui()->tabWidget_->currentWidget())->firstVisibleAddress(); } +//------------------------------------------------------------------------------ +// Name: instruction_pointer_address +// Desc: Returns the address pointed to by the instruction pointer. +//------------------------------------------------------------------------------ +address_t instruction_pointer_address() { + if (IProcess *process = debugger_core->process()) { + State state; + process->currentThread()->getState(&state); + return state.instructionPointer(); + } + + return address_t{}; +} + //------------------------------------------------------------------------------ // Name: set_status // Desc: