mirror of
https://github.com/rizinorg/cutter.git
synced 2024-12-18 19:06:10 +00:00
Breakpoint Menu and Debug Menu in the Decompiler Context Menu (#2260)
This commit is contained in:
parent
ea9f3f1831
commit
a4174271f6
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;;
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user