diff --git a/src/menus/DecompilerContextMenu.cpp b/src/menus/DecompilerContextMenu.cpp index 1e93fb48f..167111495 100644 --- a/src/menus/DecompilerContextMenu.cpp +++ b/src/menus/DecompilerContextMenu.cpp @@ -1,6 +1,7 @@ #include "DecompilerContextMenu.h" #include "dialogs/preferences/PreferencesDialog.h" #include "MainWindow.h" +#include "dialogs/BreakpointsDialog.h" #include #include @@ -12,12 +13,23 @@ DecompilerContextMenu::DecompilerContextMenu(QWidget *parent, MainWindow *mainWindow) : QMenu(parent), offset(0), + isTogglingBreakpoints(false), mainWindow(mainWindow), - actionCopy(tr("Copy"), this) + actionCopy(tr("Copy"), this), + actionToggleBreakpoint(tr("Add/remove breakpoint"), this), + actionAdvancedBreakpoint(tr("Advanced breakpoint"), this), + breakpointsInLineMenu(new QMenu(this)), + actionContinueUntil(tr("Continue until line"), this), + actionSetPC(tr("Set PC"), this) { setActionCopy(); addSeparator(); + addBreakpointMenu(); + addDebugMenu(); + + setShortcutContextInActions(this); + connect(this, &DecompilerContextMenu::aboutToShow, this, &DecompilerContextMenu::aboutToShowSlot); } @@ -33,22 +45,118 @@ void DecompilerContextMenu::setOffset(RVA offset) // this->actionSetFunctionVarTypes.setVisible(true); } +void DecompilerContextMenu::setFirstOffsetInLine(RVA firstOffset) +{ + this->firstOffsetInLine = firstOffset; +} + +void DecompilerContextMenu::setAvailableBreakpoints(QVector offsetList) +{ + this->availableBreakpoints = offsetList; +} + +void DecompilerContextMenu::setupBreakpointsInLineMenu() +{ + breakpointsInLineMenu->clear(); + for (auto curOffset : this->availableBreakpoints) { + QAction *action = breakpointsInLineMenu->addAction(RAddressString(curOffset)); + connect(action, &QAction::triggered, this, [this, curOffset] { + BreakpointsDialog::editBreakpoint(Core()->getBreakpointAt(curOffset), + this); + }); + } +} + void DecompilerContextMenu::setCanCopy(bool enabled) { actionCopy.setVisible(enabled); } +void DecompilerContextMenu::setShortcutContextInActions(QMenu *menu) +{ + for (QAction *action : menu->actions()) { + if (action->isSeparator()) { + //Do nothing + } else if (action->menu()) { + setShortcutContextInActions(action->menu()); + } else { + action->setShortcutContext(Qt::WidgetWithChildrenShortcut); + } + } +} + +void DecompilerContextMenu::setIsTogglingBreakpoints(bool isToggling) +{ + this->isTogglingBreakpoints = isToggling; +} + +bool DecompilerContextMenu::getIsTogglingBreakpoints() +{ + return this->isTogglingBreakpoints; +} + void DecompilerContextMenu::aboutToShowSlot() { + + setupBreakpointsInLineMenu(); + + // Only show debug options if we are currently debugging + debugMenu->menuAction()->setVisible(Core()->currentlyDebugging); + + bool hasBreakpoint = !this->availableBreakpoints.isEmpty(); + int numberOfBreakpoints = this->availableBreakpoints.size(); + if (numberOfBreakpoints == 0) { + actionToggleBreakpoint.setText(tr("Add breakpoint")); + } else if (numberOfBreakpoints == 1) { + actionToggleBreakpoint.setText(tr("Remove breakpoint")); + } else { + actionToggleBreakpoint.setText(tr("Remove all breakpoints in line")); + } + + if (numberOfBreakpoints > 1) { + actionAdvancedBreakpoint.setMenu(breakpointsInLineMenu); + } else { + actionAdvancedBreakpoint.setMenu(nullptr); + } + actionAdvancedBreakpoint.setText(hasBreakpoint ? + tr("Edit breakpoint") : tr("Advanced breakpoint")); + + QString progCounterName = Core()->getRegisterName("PC").toUpper(); + actionSetPC.setText(tr("Set %1 here").arg(progCounterName)); } // Set up actions -void DecompilerContextMenu::setActionCopy(){ +void DecompilerContextMenu::setActionCopy() +{ connect(&actionCopy, &QAction::triggered, this, &DecompilerContextMenu::actionCopyTriggered); addAction(&actionCopy); actionCopy.setShortcut(QKeySequence::Copy); - actionCopy.setShortcutContext(Qt::WidgetWithChildrenShortcut); +} + +void DecompilerContextMenu::setActionToggleBreakpoint() +{ + connect(&actionToggleBreakpoint, &QAction::triggered, this, + &DecompilerContextMenu::actionToggleBreakpointTriggered); + actionToggleBreakpoint.setShortcuts({Qt::Key_F2, Qt::CTRL + Qt::Key_B}); +} + +void DecompilerContextMenu::setActionAdvancedBreakpoint() +{ + connect(&actionAdvancedBreakpoint, &QAction::triggered, this, + &DecompilerContextMenu::actionAdvancedBreakpointTriggered); + actionAdvancedBreakpoint.setShortcut({Qt::CTRL + Qt::Key_F2}); +} + +void DecompilerContextMenu::setActionContinueUntil() +{ + connect(&actionContinueUntil, &QAction::triggered, this, + &DecompilerContextMenu::actionContinueUntilTriggered); +} + +void DecompilerContextMenu::setActionSetPC() +{ + connect(&actionSetPC, &QAction::triggered, this, &DecompilerContextMenu::actionSetPCTriggered); } // Set up action responses @@ -57,3 +165,65 @@ void DecompilerContextMenu::actionCopyTriggered() { emit copy(); } + +void DecompilerContextMenu::actionToggleBreakpointTriggered() +{ + if (!this->availableBreakpoints.isEmpty()) { + setIsTogglingBreakpoints(true); + for (auto offsetToRemove : this->availableBreakpoints) { + Core()->toggleBreakpoint(offsetToRemove); + } + this->availableBreakpoints.clear(); + setIsTogglingBreakpoints(false); + return; + } + if (this->firstOffsetInLine == RVA_MAX) + return; + + Core()->toggleBreakpoint(this->firstOffsetInLine); +} + +void DecompilerContextMenu::actionAdvancedBreakpointTriggered() +{ + if (!availableBreakpoints.empty()) { + // Edit the earliest breakpoint in the line + BreakpointsDialog::editBreakpoint(Core()->getBreakpointAt(this->availableBreakpoints.first()), + this); + } else { + // Add a breakpoint to the earliest offset in the line + BreakpointsDialog::createNewBreakpoint(this->firstOffsetInLine, this); + } +} + +void DecompilerContextMenu::actionContinueUntilTriggered() +{ + Core()->continueUntilDebug(RAddressString(offset)); +} + +void DecompilerContextMenu::actionSetPCTriggered() +{ + QString progCounterName = Core()->getRegisterName("PC"); + Core()->setRegister(progCounterName, RAddressString(offset).toUpper()); +} + +// Set up menus + +void DecompilerContextMenu::addBreakpointMenu() +{ + breakpointMenu = addMenu(tr("Breakpoint")); + + setActionToggleBreakpoint(); + breakpointMenu->addAction(&actionToggleBreakpoint); + setActionAdvancedBreakpoint(); + breakpointMenu->addAction(&actionAdvancedBreakpoint); +} + +void DecompilerContextMenu::addDebugMenu() +{ + debugMenu = addMenu(tr("Debug")); + + setActionContinueUntil(); + debugMenu->addAction(&actionContinueUntil); + setActionSetPC(); + debugMenu->addAction(&actionSetPC); +} diff --git a/src/menus/DecompilerContextMenu.h b/src/menus/DecompilerContextMenu.h index bc8b432ea..99d629bbf 100644 --- a/src/menus/DecompilerContextMenu.h +++ b/src/menus/DecompilerContextMenu.h @@ -13,33 +13,71 @@ class DecompilerContextMenu : public QMenu DecompilerContextMenu(QWidget *parent, MainWindow *mainWindow); ~DecompilerContextMenu(); + bool getIsTogglingBreakpoints(); + signals: void copy(); public slots: void setOffset(RVA offset); void setCanCopy(bool enabled); + void setFirstOffsetInLine(RVA firstOffset); + void setAvailableBreakpoints(QVector offsetList); + private slots: void aboutToShowSlot(); void actionCopyTriggered(); -private: - QKeySequence getCopySequence() const; + void actionToggleBreakpointTriggered(); + void actionAdvancedBreakpointTriggered(); + + void actionContinueUntilTriggered(); + void actionSetPCTriggered(); +private: + // Private variables RVA offset; + RVA firstOffsetInLine; + bool isTogglingBreakpoints; + QVector availableBreakpoints; MainWindow *mainWindow; QAction actionCopy; QAction *copySeparator; - + + QMenu *breakpointMenu; + QAction actionToggleBreakpoint; + QAction actionAdvancedBreakpoint; + + QMenu *breakpointsInLineMenu; + + QMenu *debugMenu; + QAction actionContinueUntil; + QAction actionSetPC; + + // Private Functions + void setShortcutContextInActions(QMenu *menu); + void setupBreakpointsInLineMenu(); + void setIsTogglingBreakpoints(bool isToggling); + + // Set actions void setActionCopy(); - + + void setActionToggleBreakpoint(); + void setActionAdvancedBreakpoint(); + + void setActionContinueUntil(); + void setActionSetPC(); + + // Add Menus + void addBreakpointMenu(); + void addDebugMenu(); // I left out the following part from RAnnotatedCode. Probably, we will be returning/passing annotations // from/to the function getThingUsedHere() and updateTargetMenuActions(). This block of comment will get removed in // future PRs. - // + // // struct ThingUsedHere { // QString name; // RVA offset; diff --git a/src/widgets/DecompilerWidget.cpp b/src/widgets/DecompilerWidget.cpp index 905d9880e..bd26ce5a3 100644 --- a/src/widgets/DecompilerWidget.cpp +++ b/src/widgets/DecompilerWidget.cpp @@ -19,7 +19,8 @@ DecompilerWidget::DecompilerWidget(MainWindow *main) : MemoryDockWidget(MemoryWidgetType::Decompiler, main), mCtxMenu(new DecompilerContextMenu(this, main)), ui(new Ui::DecompilerWidget), - code(Decompiler::makeWarning(tr("Choose an offset and refresh to get decompiled code")), &r_annotated_code_free) + code(Decompiler::makeWarning(tr("Choose an offset and refresh to get decompiled code")), + &r_annotated_code_free) { ui->setupUi(this); @@ -77,7 +78,9 @@ DecompilerWidget::DecompilerWidget(MainWindow *main) : ui->textEdit->setPlainText(tr("No Decompiler available.")); } - connect(ui->decompilerComboBox, static_cast(&QComboBox::currentIndexChanged), this, &DecompilerWidget::decompilerSelected); + connect(ui->decompilerComboBox, + static_cast(&QComboBox::currentIndexChanged), this, + &DecompilerWidget::decompilerSelected); connectCursorPositionChanged(false); connect(Core(), &CutterCore::seekChanged, this, &DecompilerWidget::seekChanged); ui->textEdit->setContextMenuPolicy(Qt::CustomContextMenu); @@ -86,6 +89,7 @@ DecompilerWidget::DecompilerWidget(MainWindow *main) : // refresh the widget when an action in this menu is triggered connect(mCtxMenu, &QMenu::triggered, this, &DecompilerWidget::refreshDecompiler); + connect(Core(), &CutterCore::breakpointsChanged, this, &DecompilerWidget::setInfoForBreakpoints); addActions(mCtxMenu->actions()); ui->progressLabel->setVisible(false); @@ -179,6 +183,47 @@ static size_t positionForOffset(RAnnotatedCode &codeDecompiled, ut64 offset) return closestPos; } +void DecompilerWidget::setInfoForBreakpoints() +{ + if (mCtxMenu->getIsTogglingBreakpoints()) + return; + // Get the range of the line + QTextCursor cursorForLine = ui->textEdit->textCursor(); + cursorForLine.movePosition(QTextCursor::StartOfLine); + size_t startPos = cursorForLine.position(); + cursorForLine.movePosition(QTextCursor::EndOfLine); + size_t endPos = cursorForLine.position(); + gatherBreakpointInfo(*code, startPos, endPos); +} + +void DecompilerWidget::gatherBreakpointInfo(RAnnotatedCode &codeDecompiled, size_t startPos, + size_t endPos) +{ + RVA firstOffset = RVA_MAX; + void *annotationi; + r_vector_foreach(&codeDecompiled.annotations, annotationi) { + RCodeAnnotation *annotation = (RCodeAnnotation *)annotationi; + if (annotation->type != R_CODE_ANNOTATION_TYPE_OFFSET) { + continue; + } + if ((startPos <= annotation->start && annotation->start < endPos) || (startPos <= annotation->end + && annotation->end < endPos)) { + firstOffset = (annotation->offset.offset < firstOffset) ? annotation->offset.offset : firstOffset; + } + } + mCtxMenu->setFirstOffsetInLine(firstOffset); + QList functionBreakpoints = Core()->getBreakpointsInFunction(decompiledFunctionAddr); + QVector offsetList; + for (auto bpOffset : functionBreakpoints) { + size_t pos = positionForOffset(*code, bpOffset); + if (startPos <= pos && pos <= endPos) { + offsetList.push_back(bpOffset); + } + } + std::sort(offsetList.begin(), offsetList.end()); + mCtxMenu->setAvailableBreakpoints(offsetList); +} + void DecompilerWidget::doRefresh(RVA addr) { if (!refreshDeferrer->attemptRefresh(nullptr)) { @@ -219,6 +264,7 @@ void DecompilerWidget::doRefresh(RVA addr) void DecompilerWidget::refreshDecompiler() { doRefresh(); + setInfoForBreakpoints(); } QTextCursor DecompilerWidget::getCursorForAddress(RVA addr) @@ -270,9 +316,11 @@ void DecompilerWidget::decompilerSelected() void DecompilerWidget::connectCursorPositionChanged(bool disconnect) { if (disconnect) { - QObject::disconnect(ui->textEdit, &QPlainTextEdit::cursorPositionChanged, this, &DecompilerWidget::cursorPositionChanged); + QObject::disconnect(ui->textEdit, &QPlainTextEdit::cursorPositionChanged, this, + &DecompilerWidget::cursorPositionChanged); } else { - connect(ui->textEdit, &QPlainTextEdit::cursorPositionChanged, this, &DecompilerWidget::cursorPositionChanged); + connect(ui->textEdit, &QPlainTextEdit::cursorPositionChanged, this, + &DecompilerWidget::cursorPositionChanged); } } @@ -283,7 +331,12 @@ void DecompilerWidget::cursorPositionChanged() if (!ui->textEdit->textCursor().selectedText().isEmpty()) { return; } + size_t pos = ui->textEdit->textCursor().position(); + + + setInfoForBreakpoints(); + RVA offset = offsetForPosition(*code, pos); if (offset != RVA_INVALID && offset != Core()->getOffset()) { seekFromCursor = true; @@ -380,10 +433,11 @@ void DecompilerWidget::seekToReference() bool DecompilerWidget::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::MouseButtonDblClick - && (obj == ui->textEdit || obj == ui->textEdit->viewport())) { + && (obj == ui->textEdit || obj == ui->textEdit->viewport())) { QMouseEvent *mouseEvent = static_cast(event); - const QTextCursor& cursor = ui->textEdit->cursorForPosition(QPoint(mouseEvent->x(), mouseEvent->y())); + const QTextCursor &cursor = ui->textEdit->cursorForPosition(QPoint(mouseEvent->x(), + mouseEvent->y())); seekToReference(); return true; } @@ -410,7 +464,7 @@ void DecompilerWidget::highlightBreakpoints() QList functionBreakpoints = Core()->getBreakpointsInFunction(decompiledFunctionAddr); QTextCursor cursor; - foreach(auto &bp, functionBreakpoints) { + for (auto &bp : functionBreakpoints) { if (bp == RVA_INVALID) { continue;; } diff --git a/src/widgets/DecompilerWidget.h b/src/widgets/DecompilerWidget.h index 04f80aa37..e5876a579 100644 --- a/src/widgets/DecompilerWidget.h +++ b/src/widgets/DecompilerWidget.h @@ -56,7 +56,7 @@ private slots: bool decompilerWasBusy; RVA decompiledFunctionAddr; - std::unique_ptr code; + std::unique_ptr code; bool seekFromCursor = false; Decompiler *getCurrentDecompiler(); @@ -98,7 +98,21 @@ private slots: * It will also run when a breakpoint is added, removed or modified. */ void highlightBreakpoints(); + /** + * @brief Finds the earliest offset and breakpoints within the specified range [startPos, endPos] + * in the specified RAnnotatedCode + * + * This function is supposed to be used for finding the earliest offset and breakpoints within the specified range + * [startPos, endPos]. This will set the value of the variables 'RVA firstOffsetInLine' and 'QVector availableBreakpoints' in + * this->mCtxMenu. + * + * @param codeDecompiled - A reference to the RAnnotatedCode for the function that is decompiled. + * @param startPos - Position of the start of the range(inclusive). + * @param endPos - Position of the end of the range(inclusive). + */ + void gatherBreakpointInfo(RAnnotatedCode &codeDecompiled, size_t startPos, size_t endPos); + void setInfoForBreakpoints(); }; #endif // DECOMPILERWIDGET_H