Breakpoint Menu and Debug Menu in the Decompiler Context Menu (#2260)

This commit is contained in:
NIRMAL MANOJ C 2020-06-30 00:38:02 +05:30
parent ea9f3f1831
commit a4174271f6
4 changed files with 292 additions and 16 deletions

View File

@ -1,6 +1,7 @@
#include "DecompilerContextMenu.h"
#include "dialogs/preferences/PreferencesDialog.h"
#include "MainWindow.h"
#include "dialogs/BreakpointsDialog.h"
#include <QtCore>
#include <QShortcut>
@ -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<RVA> 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);
}

View File

@ -13,33 +13,71 @@ public:
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<RVA> 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<RVA> 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;

View File

@ -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<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &DecompilerWidget::decompilerSelected);
connect(ui->decompilerComboBox,
static_cast<void(QComboBox::*)(int)>(&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<RVA> functionBreakpoints = Core()->getBreakpointsInFunction(decompiledFunctionAddr);
QVector<RVA> 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<QMouseEvent *>(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<RVA> functionBreakpoints = Core()->getBreakpointsInFunction(decompiledFunctionAddr);
QTextCursor cursor;
foreach(auto &bp, functionBreakpoints) {
for (auto &bp : functionBreakpoints) {
if (bp == RVA_INVALID) {
continue;;
}

View File

@ -56,7 +56,7 @@ private:
bool decompilerWasBusy;
RVA decompiledFunctionAddr;
std::unique_ptr<RAnnotatedCode, void (*)(RAnnotatedCode*)> code;
std::unique_ptr<RAnnotatedCode, void (*)(RAnnotatedCode *)> code;
bool seekFromCursor = false;
Decompiler *getCurrentDecompiler();
@ -98,7 +98,21 @@ private:
* 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<RVA> 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