From b7d1059a1b029160c56bbbc7545279e9cc19eccb Mon Sep 17 00:00:00 2001 From: NIRMAL MANOJ C Date: Sat, 15 Aug 2020 13:31:27 +0530 Subject: [PATCH] Decompiler Documentation + Clean up (#2374) --- src/menus/DecompilerContextMenu.cpp | 52 ++++++------- src/menus/DecompilerContextMenu.h | 56 +++++++++++++- src/widgets/DecompilerWidget.cpp | 64 ++++++---------- src/widgets/DecompilerWidget.h | 109 ++++++++++++++++++++++++++-- 4 files changed, 206 insertions(+), 75 deletions(-) diff --git a/src/menus/DecompilerContextMenu.cpp b/src/menus/DecompilerContextMenu.cpp index fe18b75e..4251e5b3 100644 --- a/src/menus/DecompilerContextMenu.cpp +++ b/src/menus/DecompilerContextMenu.cpp @@ -17,11 +17,11 @@ DecompilerContextMenu::DecompilerContextMenu(QWidget *parent, MainWindow *mainWindow) : QMenu(parent), + mainWindow(mainWindow), curHighlightedWord(QString()), offset(0), decompiledFunctionAddress(RVA_INVALID), isTogglingBreakpoints(false), - mainWindow(mainWindow), annotationHere(nullptr), actionCopy(tr("Copy"), this), actionCopyInstructionAddress(tr("Copy instruction address (
)"), this), @@ -48,11 +48,11 @@ DecompilerContextMenu::DecompilerContextMenu(QWidget *parent, MainWindow *mainWi setActionAddComment(); setActionDeleteComment(); - setActionXRefs(); - setActionRenameThingHere(); setActionDeleteName(); + setActionXRefs(); + setActionEditFunctionVariables(); addSeparator(); @@ -73,34 +73,37 @@ DecompilerContextMenu::~DecompilerContextMenu() void DecompilerContextMenu::setAnnotationHere(RCodeAnnotation *annotation) { - this->annotationHere = annotation; + annotationHere = annotation; } void DecompilerContextMenu::setCurHighlightedWord(QString word) { - this->curHighlightedWord = word; + curHighlightedWord = word; } -void DecompilerContextMenu::setOffset(RVA offset) +void DecompilerContextMenu::setOffset(RVA newOffset) { - this->offset = offset; - - // this->actionSetFunctionVarTypes.setVisible(true); + offset = newOffset; } void DecompilerContextMenu::setDecompiledFunctionAddress(RVA functionAddr) { - this->decompiledFunctionAddress = functionAddr; + decompiledFunctionAddress = functionAddr; } void DecompilerContextMenu::setFirstOffsetInLine(RVA firstOffset) { - this->firstOffsetInLine = firstOffset; + firstOffsetInLine = firstOffset; +} + +RVA DecompilerContextMenu::getFirstOffsetInLine() +{ + return firstOffsetInLine; } void DecompilerContextMenu::setAvailableBreakpoints(QVector offsetList) { - this->availableBreakpoints = offsetList; + availableBreakpoints = offsetList; } void DecompilerContextMenu::setupBreakpointsInLineMenu() @@ -130,12 +133,12 @@ void DecompilerContextMenu::setShortcutContextInActions(QMenu *menu) void DecompilerContextMenu::setIsTogglingBreakpoints(bool isToggling) { - this->isTogglingBreakpoints = isToggling; + isTogglingBreakpoints = isToggling; } bool DecompilerContextMenu::getIsTogglingBreakpoints() { - return this->isTogglingBreakpoints; + return isTogglingBreakpoints; } void DecompilerContextMenu::aboutToHideSlot() @@ -169,7 +172,6 @@ void DecompilerContextMenu::aboutToShowSlot() actionDeleteComment.setVisible(false); } - setupBreakpointsInLineMenu(); // Only show debug options if we are currently debugging @@ -184,7 +186,6 @@ void DecompilerContextMenu::aboutToShowSlot() } else { actionToggleBreakpoint.setText(tr("Remove all breakpoints in line")); } - if (numberOfBreakpoints > 1) { actionAdvancedBreakpoint.setMenu(breakpointsInLineMenu); } else { @@ -198,7 +199,7 @@ void DecompilerContextMenu::aboutToShowSlot() if (!annotationHere || annotationHere->type == - R_CODE_ANNOTATION_TYPE_CONSTANT_VARIABLE) { // To be considered as invalid + R_CODE_ANNOTATION_TYPE_CONSTANT_VARIABLE) { // If constant, don't show rename and targeted show-in actionRenameThingHere.setVisible(false); copySeparator->setVisible(false); } else { @@ -219,7 +220,7 @@ void DecompilerContextMenu::aboutToShowSlot() } actionCopyInstructionAddress.setText(tr("Copy instruction address (%1)").arg(RAddressString( offset))); - if (annotationHere && r_annotation_is_reference(annotationHere)) { + if (isReference()) { actionCopyReferenceAddress.setVisible(true); RVA referenceAddr = annotationHere->reference.offset; RFlagItem *flagDetails = r_flag_get_i(Core()->core()->flags, referenceAddr); @@ -258,7 +259,6 @@ void DecompilerContextMenu::aboutToShowSlot() // Set up actions - void DecompilerContextMenu::setActionCopy() // Set all three copy actions { connect(&actionCopy, &QAction::triggered, this, &DecompilerContextMenu::actionCopyTriggered); @@ -386,7 +386,7 @@ void DecompilerContextMenu::actionDeleteCommentTriggered() void DecompilerContextMenu::actionRenameThingHereTriggered() { - if (!annotationHere) { + if (!annotationHere || annotationHere->type == R_CODE_ANNOTATION_TYPE_CONSTANT_VARIABLE) { return; } RCoreLocked core = Core()->core(); @@ -410,7 +410,6 @@ void DecompilerContextMenu::actionRenameThingHereTriggered() Core()->renameFunction(func_addr, newName); } } - } else if (type == R_CODE_ANNOTATION_TYPE_GLOBAL_VARIABLE) { RVA var_addr = annotationHere->reference.offset; RFlagItem *flagDetails = r_flag_get_i(core->flags, var_addr); @@ -467,7 +466,7 @@ void DecompilerContextMenu::actionEditFunctionVariablesTriggered() void DecompilerContextMenu::actionXRefsTriggered() { - if (!annotationHere || !r_annotation_is_reference(annotationHere)) { + if (!isReference()) { return; } XrefsDialog dialog(mainWindow, nullptr); @@ -551,9 +550,7 @@ void DecompilerContextMenu::updateTargetMenuActions() } showTargetMenuActions.clear(); RCoreLocked core = Core()->core(); - if (annotationHere && (annotationHere->type == R_CODE_ANNOTATION_TYPE_GLOBAL_VARIABLE - || annotationHere->type == R_CODE_ANNOTATION_TYPE_CONSTANT_VARIABLE - || annotationHere->type == R_CODE_ANNOTATION_TYPE_FUNCTION_NAME)) { + if (isReference()) { QString name; QMenu *menu; if (annotationHere->type == R_CODE_ANNOTATION_TYPE_GLOBAL_VARIABLE @@ -580,6 +577,11 @@ void DecompilerContextMenu::updateTargetMenuActions() } } +bool DecompilerContextMenu::isReference() +{ + return (annotationHere && r_annotation_is_reference(annotationHere)); +} + bool DecompilerContextMenu::isFunctionVariable() { return (annotationHere && r_annotation_is_variable(annotationHere)); diff --git a/src/menus/DecompilerContextMenu.h b/src/menus/DecompilerContextMenu.h index 86402e40..132993cf 100644 --- a/src/menus/DecompilerContextMenu.h +++ b/src/menus/DecompilerContextMenu.h @@ -7,6 +7,8 @@ #include +class MainWindow; + class DecompilerContextMenu : public QMenu { Q_OBJECT @@ -17,18 +19,18 @@ public: bool getIsTogglingBreakpoints(); void setAnnotationHere(RCodeAnnotation *annotation); + RVA getFirstOffsetInLine(); signals: void copy(); public slots: void setCurHighlightedWord(QString word); - void setOffset(RVA offset); + void setOffset(RVA newOffset); void setDecompiledFunctionAddress(RVA functionAddr); void setFirstOffsetInLine(RVA firstOffset); void setAvailableBreakpoints(QVector offsetList); - private slots: void aboutToShowSlot(); void aboutToHideSlot(); @@ -55,16 +57,30 @@ private slots: private: // Private variables + MainWindow *mainWindow; QString curHighlightedWord; RVA offset; RVA decompiledFunctionAddress; + /** + * Lowest offset among all offsets present in the line under cursor in the decompiler widget. + */ RVA firstOffsetInLine; + /** + * When the actionToggleBreakpoint has been triggered, and it hasn't finished executing, + * the value of this variable will be true, otherwise false + */ bool isTogglingBreakpoints; + /** + * List of the offsets of all the breakpoints (enabled and disabled) that are present in the line under cursor. + */ QVector availableBreakpoints; - MainWindow *mainWindow; - + /** + * Context-related annotation for the data under cursor in the decompiler widget. + * If such an annotation doesn't exist, its value is nullptr. + */ RCodeAnnotation *annotationHere; + // Actions and menus in the context menu QAction actionCopy; QAction actionCopyInstructionAddress; QAction actionCopyReferenceAddress; @@ -94,6 +110,12 @@ private: QAction actionSetPC; // Private Functions + /** + * @brief Sets the shortcut context in all the actions contained + * in the specified QMenu to Qt::WidgetWithChildrenShortcut. + * + * @param menu - QMenu specified + */ void setShortcutContextInActions(QMenu *menu); void setupBreakpointsInLineMenu(); void setIsTogglingBreakpoints(bool isToggling); @@ -123,9 +145,35 @@ private: void addBreakpointMenu(); void addDebugMenu(); + /** + * @brief Updates targeted "Show in" menu. + * + * Removes all actions from the existing targeted "show in" menu. If annotationHere + * represents an item that has an address assigned to it, insert actions compatible with the + * type of this item in the targeted "Show in" menu. + */ void updateTargetMenuActions(); + /** + * @brief Check if annotationHere is a reference (function name, + * global variable, constant variable with an address). + * + * @return True if annotationHere is a reference, otherwise false. + */ + bool isReference(); + /** + * @brief Check if annotationHere is a function variable + * (local variable or function parameter). + * + * @return True if annotationHere is a function variable, otherwise false. + */ bool isFunctionVariable(); + /** + * @brief Check if the function variable annotated by annotationHere is + * present in radare2. + * + * @return True if the variable is present, otherwise false + */ bool variablePresentInR2(); }; diff --git a/src/widgets/DecompilerWidget.cpp b/src/widgets/DecompilerWidget.cpp index e55d58ce..d1411c1c 100644 --- a/src/widgets/DecompilerWidget.cpp +++ b/src/widgets/DecompilerWidget.cpp @@ -31,10 +31,8 @@ DecompilerWidget::DecompilerWidget(MainWindow *main) : &r_annotated_code_free) { ui->setupUi(this); - syntaxHighlighter = Config()->createSyntaxHighlighter(ui->textEdit->document()); - - // Event filter to intercept double clicks in the textbox + // Event filter to intercept double click and right click in the textbox ui->textEdit->viewport()->installEventFilter(this); setupFonts(); @@ -48,11 +46,9 @@ DecompilerWidget::DecompilerWidget(MainWindow *main) : connect(ui->refreshButton, &QAbstractButton::clicked, this, [this]() { doRefresh(); }); - refreshDeferrer = createRefreshDeferrer([this]() { doRefresh(); }); - autoRefreshEnabled = Config()->getDecompilerAutoRefreshEnabled(); ui->autoRefreshCheckBox->setChecked(autoRefreshEnabled); setAutoRefresh(autoRefreshEnabled); @@ -75,10 +71,8 @@ DecompilerWidget::DecompilerWidget(MainWindow *main) : } connect(dec, &Decompiler::finished, this, &DecompilerWidget::decompilationFinished); } - decompilerSelectionEnabled = decompilers.size() > 1; ui->decompilerComboBox->setEnabled(decompilerSelectionEnabled); - if (decompilers.isEmpty()) { ui->textEdit->setPlainText(tr("No Decompiler available.")); } @@ -90,7 +84,7 @@ DecompilerWidget::DecompilerWidget(MainWindow *main) : connect(Core(), &CutterCore::seekChanged, this, &DecompilerWidget::seekChanged); ui->textEdit->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->textEdit, &QWidget::customContextMenuRequested, - this, &DecompilerWidget::showDisasContextMenu); + this, &DecompilerWidget::showDecompilerContextMenu); connect(Core(), &CutterCore::breakpointsChanged, this, &DecompilerWidget::updateBreakpoints); addActions(mCtxMenu->actions()); @@ -147,13 +141,13 @@ void DecompilerWidget::updateRefreshButton() } } -static ut64 offsetForPosition(RAnnotatedCode &codeDecompiled, size_t pos) +ut64 DecompilerWidget::offsetForPosition(size_t pos) { size_t closestPos = SIZE_MAX; - ut64 closestOffset = UT64_MAX; - void *annotationi; - r_vector_foreach(&codeDecompiled.annotations, annotationi) { - RCodeAnnotation *annotation = (RCodeAnnotation *)annotationi; + ut64 closestOffset = mCtxMenu->getFirstOffsetInLine(); + void *iter; + r_vector_foreach(&code->annotations, iter) { + RCodeAnnotation *annotation = (RCodeAnnotation *)iter; if (annotation->type != R_CODE_ANNOTATION_TYPE_OFFSET || annotation->start > pos || annotation->end <= pos) { continue; @@ -167,13 +161,13 @@ static ut64 offsetForPosition(RAnnotatedCode &codeDecompiled, size_t pos) return closestOffset; } -static size_t positionForOffset(RAnnotatedCode &codeDecompiled, ut64 offset) +size_t DecompilerWidget::positionForOffset(ut64 offset) { size_t closestPos = SIZE_MAX; ut64 closestOffset = UT64_MAX; - void *annotationi; - r_vector_foreach(&codeDecompiled.annotations, annotationi) { - RCodeAnnotation *annotation = (RCodeAnnotation *)annotationi; + void *iter; + r_vector_foreach(&code->annotations, iter) { + RCodeAnnotation *annotation = (RCodeAnnotation *)iter; if (annotation->type != R_CODE_ANNOTATION_TYPE_OFFSET || annotation->offset.offset > offset) { continue; } @@ -216,13 +210,13 @@ void DecompilerWidget::gatherBreakpointInfo(RAnnotatedCode &codeDecompiled, size size_t endPos) { RVA firstOffset = RVA_MAX; - void *annotationi; - r_vector_foreach(&codeDecompiled.annotations, annotationi) { - RCodeAnnotation *annotation = (RCodeAnnotation *)annotationi; + void *iter; + r_vector_foreach(&codeDecompiled.annotations, iter) { + RCodeAnnotation *annotation = (RCodeAnnotation *)iter; if (annotation->type != R_CODE_ANNOTATION_TYPE_OFFSET) { continue; } - if ((startPos <= annotation->start && annotation->start < endPos) || (startPos <= annotation->end + if ((startPos <= annotation->start && annotation->start < endPos) || (startPos < annotation->end && annotation->end < endPos)) { firstOffset = (annotation->offset.offset < firstOffset) ? annotation->offset.offset : firstOffset; } @@ -231,7 +225,7 @@ void DecompilerWidget::gatherBreakpointInfo(RAnnotatedCode &codeDecompiled, size QList functionBreakpoints = Core()->getBreakpointsInFunction(decompiledFunctionAddr); QVector offsetList; for (auto bpOffset : functionBreakpoints) { - size_t pos = positionForOffset(*code, bpOffset); + size_t pos = positionForOffset(bpOffset); if (startPos <= pos && pos <= endPos) { offsetList.push_back(bpOffset); } @@ -245,26 +239,21 @@ void DecompilerWidget::doRefresh(RVA addr) if (!refreshDeferrer->attemptRefresh(nullptr)) { return; } - if (ui->decompilerComboBox->currentIndex() < 0) { return; } - Decompiler *dec = getCurrentDecompiler(); if (!dec) { return; } - if (dec->isRunning()) { decompilerWasBusy = true; return; } - if (addr == RVA_INVALID) { ui->textEdit->setPlainText(tr("Click Refresh to generate Decompiler from current offset.")); return; } - // Clear all selections since we just refreshed ui->textEdit->setExtraSelections({}); previousFunctionAddr = decompiledFunctionAddr; @@ -287,11 +276,10 @@ void DecompilerWidget::refreshDecompiler() QTextCursor DecompilerWidget::getCursorForAddress(RVA addr) { - size_t pos = positionForOffset(*code, addr); + size_t pos = positionForOffset(addr); if (pos == SIZE_MAX || pos == 0) { return QTextCursor(); } - QTextCursor cursor = ui->textEdit->textCursor(); cursor.setPosition(pos); return cursor; @@ -339,9 +327,9 @@ void DecompilerWidget::decompilationFinished(RAnnotatedCode *codeDecompiled) void DecompilerWidget::setAnnotationsAtCursor(size_t pos) { RCodeAnnotation *annotationAtPos = nullptr; - void *annotationi; - r_vector_foreach(&this->code->annotations, annotationi) { - RCodeAnnotation *annotation = (RCodeAnnotation *)annotationi; + void *iter; + r_vector_foreach(&this->code->annotations, iter) { + RCodeAnnotation *annotation = (RCodeAnnotation *)iter; if (annotation->type == R_CODE_ANNOTATION_TYPE_OFFSET || annotation->type == R_CODE_ANNOTATION_TYPE_SYNTAX_HIGHLIGHT || annotation->start > pos || annotation->end <= pos) { @@ -381,10 +369,9 @@ void DecompilerWidget::cursorPositionChanged() size_t pos = ui->textEdit->textCursor().position(); setAnnotationsAtCursor(pos); - setInfoForBreakpoints(); - RVA offset = offsetForPosition(*code, pos); + RVA offset = offsetForPosition(pos); if (offset != RVA_INVALID && offset != Core()->getOffset()) { seekFromCursor = true; Core()->seek(offset); @@ -399,7 +386,6 @@ void DecompilerWidget::seekChanged() if (seekFromCursor) { return; } - if (autoRefreshEnabled) { auto fcnAddr = Core()->getFunctionStart(Core()->getOffset()); if (fcnAddr == RVA_INVALID || fcnAddr != decompiledFunctionAddr) { @@ -407,14 +393,13 @@ void DecompilerWidget::seekChanged() return; } } - updateCursorPosition(); } void DecompilerWidget::updateCursorPosition() { RVA offset = Core()->getOffset(); - size_t pos = positionForOffset(*code, offset); + size_t pos = positionForOffset(offset); if (pos == SIZE_MAX) { return; } @@ -465,7 +450,7 @@ void DecompilerWidget::colorsUpdatedSlot() { } -void DecompilerWidget::showDisasContextMenu(const QPoint &pt) +void DecompilerWidget::showDecompilerContextMenu(const QPoint &pt) { mCtxMenu->exec(ui->textEdit->mapToGlobal(pt)); } @@ -473,7 +458,7 @@ void DecompilerWidget::showDisasContextMenu(const QPoint &pt) void DecompilerWidget::seekToReference() { size_t pos = ui->textEdit->textCursor().position(); - RVA offset = offsetForPosition(*code, pos); + RVA offset = offsetForPosition(pos); seekable->seekToReference(offset); } @@ -520,7 +505,6 @@ void DecompilerWidget::highlightBreakpoints() if (bp == RVA_INVALID) { continue;; } - cursor = getCursorForAddress(bp); if (!cursor.isNull()) { // Use a Block formatting since these lines are not updated frequently as selections and PC diff --git a/src/widgets/DecompilerWidget.h b/src/widgets/DecompilerWidget.h index 3109fd3c..46c60c7e 100644 --- a/src/widgets/DecompilerWidget.h +++ b/src/widgets/DecompilerWidget.h @@ -28,16 +28,30 @@ public: explicit DecompilerWidget(MainWindow *main); ~DecompilerWidget(); public slots: - void showDisasContextMenu(const QPoint &pt); + void showDecompilerContextMenu(const QPoint &pt); void highlightPC(); private slots: + /** + * @brief Copy to clipboard what's needed depending on the state of text widget. + * + * @note If something is selected in the text widget, copy selection. + * If something is highlighted, copy highlighted word. + * Otherwise, copy the line under cursor. + */ void copy(); void fontsUpdatedSlot(); void colorsUpdatedSlot(); void refreshDecompiler(); void decompilerSelected(); void cursorPositionChanged(); + /** + * @brief When the synced seek is changed, this refreshes the decompiler widget if needed. + * + * Decompiler widget is not refreshed in the following two cases + * - Seek changed to an offset contained in the decompiled function. + * - Auto-refresh is disabled. + */ void seekChanged(); void decompilationFinished(RAnnotatedCode *code); @@ -51,7 +65,7 @@ private: bool autoRefreshEnabled; /** - * True if doRefresh() was called, but the decompiler was still running + * True if doRefresh() was called, but the decompiler was still running. * This means, after the decompiler has finished, it should be refreshed immediately. */ bool decompilerWasBusy; @@ -65,16 +79,71 @@ private: Decompiler *getCurrentDecompiler(); + /** + * @brief Enable/Disable auto refresh as per the specified boolean value + * + * @param enabled + */ void setAutoRefresh(bool enabled); + /** + * @brief Calls the function doRefresh() if auto-refresh is enabled. + */ void doAutoRefresh(); + /** + * @brief Refreshes the decompiler. + * + * - This does the following if the specified offset is valid + * - Decompile function that contains the specified offset. + * - Clears all selections stored for highlighting purposes. + * - Reset previousFunctionAddr with the current function's address + * and decompiledFunctionAddr with the address of the function that + * was decompiled. + * - If the offset is invalid, error message is shown in the text widget. + * + * @param addr Specified offset/offset in sync. + */ void doRefresh(RVA addr = Core()->getOffset()); void updateRefreshButton(); + /** + * @brief Update fonts + */ void setupFonts(); + /** + * @brief Update highlights in the text widget. + * + * These include respective highlights for: + * - Line under cursor + * - Word under cursor + * - Program Counter(PC) while debugging + */ void updateSelection(); + /** + * @brief Connect/Disconnect SIGNAL-SLOT connection that deals with changes in cursor position. + * + * If the argument is true, then disconnect the SIGNAL-SLOT connection + * that changes the view as cursor position gets changed in the text widget. + * Otherwise, connect the corresponding signal with slot. + * + * @param disconnect + */ void connectCursorPositionChanged(bool disconnect); + /** + * @brief Find the current global offset in sync and update cursor + * to the position specified by this offset (found using positionForOffset() ) + */ void updateCursorPosition(); QString getWindowTitle() const override; + + /** + * @brief Event filter that intercept the following events: + * 1. Double click + * 2. Right click + * + * @param obj + * @param event + * @return + */ bool eventFilter(QObject *obj, QEvent *event) override; /** @@ -104,21 +173,49 @@ private: void highlightBreakpoints(); /** * @brief Finds the earliest offset and breakpoints within the specified range [startPos, endPos] - * in the specified RAnnotatedCode + * 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. + * the context menu. * * @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); - + /** + * @brief Finds the offset that's closest to the specified position in the decompiled code. + * + * @note If no annotations that covers the specified position is found, the first offset in the line + * containing specified position will be returned + * + * @param pos - Position of the decompiled code. + * @return Offset for the specified position/first offset in line. + */ + ut64 offsetForPosition(size_t pos); + /** + * @brief Find the start position of the annotation with the offset that's closest to + * the specified offset + * + * @param offset + * @return Position found or SIZE_MAX + */ + size_t positionForOffset(ut64 offset); + /** + * @brief Updates the view when breakpoints are changed + */ void updateBreakpoints(); + /** + * @brief Set information about the breakpoints on the line in the context menu + */ void setInfoForBreakpoints(); - + /** + * @brief Find the context-related annotation covering the specified position. + * If found, set the variable annotationHere in the decompiler context menu. + * + * @param pos Position of cursor in the decompiled code. + */ void setAnnotationsAtCursor(size_t pos); };