diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 8dbacfa0..f83286ad 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -20,6 +20,7 @@ Q_GLOBAL_STATIC(CutterCore, uniqueInstance) namespace RJsonKey { R_JSON_KEY(addr); R_JSON_KEY(addr_end); + R_JSON_KEY(arrow); R_JSON_KEY(baddr); R_JSON_KEY(bind); R_JSON_KEY(blocks); @@ -2708,6 +2709,7 @@ QList CutterCore::disassembleLines(RVA offset, int lines) DisassemblyLine line; line.offset = object[RJsonKey::offset].toVariant().toULongLong(); line.text = ansiEscapeToHtml(object[RJsonKey::text].toString()); + line.arrow = object[RJsonKey::arrow].toVariant().toULongLong(); r << line; } diff --git a/src/core/CutterDescriptions.h b/src/core/CutterDescriptions.h index b5efa75e..904d722a 100644 --- a/src/core/CutterDescriptions.h +++ b/src/core/CutterDescriptions.h @@ -186,6 +186,7 @@ struct RAsmPluginDescription { struct DisassemblyLine { RVA offset; QString text; + RVA arrow; }; struct BinClassBaseClassDescription { diff --git a/src/widgets/DisassemblyWidget.cpp b/src/widgets/DisassemblyWidget.cpp index 76a9013c..44d4ede9 100644 --- a/src/widgets/DisassemblyWidget.cpp +++ b/src/widgets/DisassemblyWidget.cpp @@ -12,6 +12,8 @@ #include #include #include +#include +#include class DisassemblyTextBlockUserData: public QTextBlockUserData @@ -52,13 +54,28 @@ DisassemblyWidget::DisassemblyWidget(MainWindow *main, QAction *action) setWindowTitle(getWindowTitle()); - QVBoxLayout *layout = new QVBoxLayout(); + // Instantiate the window layout + auto *splitter = new QSplitter; + + // Setup the left frame that contains breakpoints and jumps + leftPanel = new DisassemblyLeftPanel(this); + splitter->addWidget(leftPanel); + + // Setup the disassembly content + auto *layout = new QHBoxLayout; layout->addWidget(mDisasTextEdit); layout->setMargin(0); mDisasScrollArea->viewport()->setLayout(layout); - mDisasScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + splitter->addWidget(mDisasScrollArea); + + // Set current widget to the splitted layout we just created + setWidget(splitter); - setWidget(mDisasScrollArea); + // Resize properly + QList sizes; + sizes.append(3); + sizes.append(1); // TODO Probably not the best way to go + splitter->setSizes(sizes); setAllowedAreas(Qt::AllDockWidgetAreas); @@ -214,6 +231,16 @@ QString DisassemblyWidget::getWidgetType() return "Disassembly"; } +QFontMetrics DisassemblyWidget::getFontMetrics() +{ + return mDisasTextEdit->fontMetrics(); +} + +QList DisassemblyWidget::getLines() +{ + return lines; +} + void DisassemblyWidget::refreshDisasm(RVA offset) { if(!disasmRefresh->attemptRefresh(offset == RVA_INVALID ? nullptr : new RVA(offset))) { @@ -239,11 +266,12 @@ void DisassemblyWidget::refreshDisasm(RVA offset) int horizontalScrollValue = mDisasTextEdit->horizontalScrollBar()->value(); mDisasTextEdit->setLockScroll(true); // avoid flicker - QList disassemblyLines; + // Retrieve disassembly lines { TempConfig tempConfig; - tempConfig.set("scr.color", COLOR_MODE_16M); - disassemblyLines = Core()->disassembleLines(topOffset, maxLines); + tempConfig.set("scr.color", COLOR_MODE_16M) + .set("asm.lines", false); + lines = Core()->disassembleLines(topOffset, maxLines); } connectCursorPositionChanged(true); @@ -251,7 +279,7 @@ void DisassemblyWidget::refreshDisasm(RVA offset) mDisasTextEdit->document()->clear(); QTextCursor cursor(mDisasTextEdit->document()); QTextBlockFormat regular = cursor.blockFormat(); - for (const DisassemblyLine &line : disassemblyLines) { + for (const DisassemblyLine &line : lines) { if (line.offset < topOffset) { // overflow break; } @@ -267,8 +295,8 @@ void DisassemblyWidget::refreshDisasm(RVA offset) cursor.setBlockFormat(regular); } - if (!disassemblyLines.isEmpty()) { - bottomOffset = disassemblyLines[qMin(disassemblyLines.size(), maxLines) - 1].offset; + if (!lines.isEmpty()) { + bottomOffset = lines[qMin(lines.size(), maxLines) - 1].offset; if (bottomOffset < topOffset) { bottomOffset = RVA_MAX; } @@ -290,6 +318,9 @@ void DisassemblyWidget::refreshDisasm(RVA offset) mDisasTextEdit->setLockScroll(false); mDisasTextEdit->horizontalScrollBar()->setValue(horizontalScrollValue); + + // Refresh the left panel (trigger paintEvent) + leftPanel->update(); } @@ -729,3 +760,87 @@ void DisassemblyWidget::seekPrev() { Core()->seekPrev(); } + +/********************* + * Left panel + *********************/ +DisassemblyLeftPanel::DisassemblyLeftPanel(DisassemblyWidget *disas) +{ + this->disas = disas; +} + +void DisassemblyLeftPanel::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + RVA currentOffset = Core()->getOffset(); // TODO Use the seekable from DisassemblyWidget + int rightOffset = size().rwidth(); + int lineHeight = disas->getFontMetrics().height(); + QColor arrowColor = ConfigColor("flow"); + QPainter p(this); + QPen pen(arrowColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::RoundJoin); + p.setPen(pen); + + QList lines = disas->getLines(); + + // Precompute pixel position of the arrows + // TODO This can probably be done in another loop for performance purposes + QMap linesPixPosition; + int i = 0; + int baseOffset = lineHeight / 2; + for (auto l : lines) { + linesPixPosition[l.offset] = i * lineHeight + baseOffset; + i++; + } + + // Draw the lines + // TODO Use better var names + QPolygon arrow; + int direction; + int lineFinalHeight; + int lineOffset = 10; + for (auto l : lines) { + // Skip until we reach a line that jumps to a destination + if (l.arrow == 0) { + continue; + } + + // Compute useful variables + if (l.arrow > currentOffset) { + direction = 1; + } else { + direction = -1; + } + + bool endVisible = true; + int currentLineYPos = linesPixPosition[l.offset]; + lineFinalHeight = linesPixPosition.value(l.arrow, -1); + + if (lineFinalHeight == -1) { + if (direction == 1) { + lineFinalHeight = 0; + } else { + lineFinalHeight = size().height(); + } + endVisible = false; + } + + // Draw the lines + p.drawLine(rightOffset, currentLineYPos, rightOffset - lineOffset, currentLineYPos); + p.drawLine(rightOffset - lineOffset, currentLineYPos, rightOffset - lineOffset, lineFinalHeight); + + if (endVisible) { + p.drawLine(rightOffset - lineOffset, lineFinalHeight, rightOffset, lineFinalHeight); + + // Draw the arrow + arrow.clear(); + arrow.append(QPoint(rightOffset - 3, lineFinalHeight + 3)); + arrow.append(QPoint(rightOffset - 3, lineFinalHeight - 3)); + arrow.append(QPoint(rightOffset, lineFinalHeight)); + } + p.drawConvexPolygon(arrow); + + // Shift next jump line + lineOffset += 10; + } +} diff --git a/src/widgets/DisassemblyWidget.h b/src/widgets/DisassemblyWidget.h index 5906deaf..b25d565c 100644 --- a/src/widgets/DisassemblyWidget.h +++ b/src/widgets/DisassemblyWidget.h @@ -5,6 +5,7 @@ #include "MemoryDockWidget.h" #include "common/CutterSeekable.h" #include "common/RefreshDeferrer.h" +#include "common/CachedFontMetrics.h" #include #include @@ -15,6 +16,7 @@ class DisassemblyTextEdit; class DisassemblyScrollArea; class DisassemblyContextMenu; +class DisassemblyLeftPanel; class DisassemblyWidget : public MemoryDockWidget { @@ -32,6 +34,8 @@ public slots: void colorsUpdatedSlot(); void seekPrev(); void setPreviewMode(bool previewMode); + QFontMetrics getFontMetrics(); + QList getLines(); protected slots: void on_seekChanged(RVA offset); @@ -49,6 +53,8 @@ protected: DisassemblyContextMenu *mCtxMenu; DisassemblyScrollArea *mDisasScrollArea; DisassemblyTextEdit *mDisasTextEdit; + DisassemblyLeftPanel *leftPanel; + QList lines; private: RVA topOffset; @@ -128,4 +134,18 @@ private: bool lockScroll; }; +/** + * @class This class is used to draw the left pane of the disassembly + * widget. Its goal is to draw proper arrows for the jumps of the disassembly. + */ +class DisassemblyLeftPanel: public QFrame +{ +public: + DisassemblyLeftPanel(DisassemblyWidget *disas); + void paintEvent(QPaintEvent *event) override; + +private: + DisassemblyWidget *disas; +}; + #endif // DISASSEMBLYWIDGET_H