From 86d467daf9f90a2072fbbd211c71d616282d5f09 Mon Sep 17 00:00:00 2001 From: "Thomas (nezza-_-) Roth" Date: Wed, 13 Dec 2017 23:38:46 +0100 Subject: [PATCH] DisassemblerGraphView: lots of additions.. (#213) * GraphView: Add refactored graphview with animations etc. * GraphView: Use new graphview and render background color. * DisassemblerGraphView: t/f navigation, zoom with +/-, bugfixes. --- README.md | 22 + src/MainWindow.cpp | 1 + src/MainWindow.h | 1 - src/cutter.pro | 6 +- src/widgets/DisassemblerGraphView.cpp | 2165 +++++-------------------- src/widgets/DisassemblerGraphView.h | 572 +++---- src/widgets/GraphView.cpp | 892 ++++++++++ src/widgets/GraphView.h | 164 ++ 8 files changed, 1650 insertions(+), 2173 deletions(-) create mode 100644 src/widgets/GraphView.cpp create mode 100644 src/widgets/GraphView.h diff --git a/README.md b/README.md index 58ba2bbe..911b696f 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,28 @@ If you encounter the `Project ERROR: r_core development package not found` try t Cutter is developed on OS X, Linux and Windows. The first release for users will include installers for all three platforms. +## Keyboard shortcuts + +| Shortcut | Function | +| --- | --- | +| Global shortcuts: || +| . | Focus console input | +| G & S | Focus search bar | +| : | Show commands | +| F5 | Refresh contents | +| Disassembly view: || +| Esc | Seek to previous position | +| Space | Switch to disassembly graph view | +| Ctrl/Cmd+C | Copy | +| ; | Add comment | +| N | Rename current function/flag | +| Shift+N | Rename flag/function used here | +| X | Show Xrefs | +| Disassembly graph view: || +| Esc | Seek to previous position | +| Space | Switch to disassembly view | + + ## Help Right now the best place to obtain help from *cutter* developers and community is joining this telegram group: diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 40c770c0..76e5daee 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -39,6 +39,7 @@ #include "utils/Helpers.h" #include "dialogs/NewFileDialog.h" +#include "widgets/DisassemblerGraphView.h" #include "widgets/FunctionsWidget.h" #include "widgets/SectionsWidget.h" #include "widgets/CommentsWidget.h" diff --git a/src/MainWindow.h b/src/MainWindow.h index 8a8859e8..a700f9fa 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -5,7 +5,6 @@ #include "cutter.h" // only needed for ut64 #include "widgets/DisassemblyWidget.h" -#include "widgets/DisassemblerGraphView.h" #include "widgets/SidebarWidget.h" #include "widgets/HexdumpWidget.h" #include "widgets/PseudocodeWidget.h" diff --git a/src/cutter.pro b/src/cutter.pro index 5f2e05ba..9cf6f939 100644 --- a/src/cutter.pro +++ b/src/cutter.pro @@ -79,7 +79,8 @@ SOURCES += \ utils/TempConfig.cpp \ utils/SvgIconEngine.cpp \ widgets/PseudocodeWidget.cpp \ - widgets/VisualNavbar.cpp + widgets/VisualNavbar.cpp \ + widgets/GraphView.cpp HEADERS += \ cutter.h \ @@ -129,7 +130,8 @@ HEADERS += \ utils/TempConfig.h \ utils/SvgIconEngine.h \ widgets/PseudocodeWidget.h \ - widgets/VisualNavbar.h + widgets/VisualNavbar.h \ + widgets/GraphView.h FORMS += \ dialogs/AboutDialog.ui \ diff --git a/src/widgets/DisassemblerGraphView.cpp b/src/widgets/DisassemblerGraphView.cpp index 2c45e797..4c4d08ce 100644 --- a/src/widgets/DisassemblerGraphView.cpp +++ b/src/widgets/DisassemblerGraphView.cpp @@ -1,69 +1,34 @@ -/* x64dbg DisassemblerGraphView */ #include "DisassemblerGraphView.h" -#include "menus/DisassemblyContextMenu.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "dialogs/XrefsDialog.h" -#include "utils/Configuration.h" -#include "utils/Colors.h" -#ifdef _WIN32 -#undef min -#undef max -#endif +#include +#include +#include +#include +#include +#include + +#include "cutter.h" +#include "utils/Colors.h" +#include "utils/Configuration.h" +#include "utils/CachedFontMetrics.h" DisassemblerGraphView::DisassemblerGraphView(QWidget *parent) - : QAbstractScrollArea(parent), - //currentGraph(duint(0)), - //disasm(ConfigUint("Disassembler", "MaxModuleSize")), + : GraphView(parent), mFontMetrics(nullptr), - syncOrigin(false), - mCip(0), - forceCenter(false), - mHistoryLock(false), - layoutType(LayoutType::Medium), - mGoto(nullptr), mMenu(new DisassemblyContextMenu(this)) { - this->status = "Loading..."; - - //Start disassembly view at the entry point of the binary - this->function = 0; - this->update_id = 0; - this->ready = false; - this->desired_pos = nullptr; this->highlight_token = nullptr; - this->cur_instr = 0; - this->scroll_base_x = 0; - this->scroll_base_y = 0; - this->scroll_mode = false; - this->drawOverview = false; - this->onlySummary = false; - this->blocks.clear(); - this->saveGraph = false; - - this->initFont(); - - //Initialize scroll bars - this->width = 0; - this->height = 0; - this->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); - this->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - this->horizontalScrollBar()->setSingleStep(this->charWidth); - this->verticalScrollBar()->setSingleStep(this->charHeight); - QSize areaSize = this->viewport()->size(); - this->adjustSize(areaSize.width(), areaSize.height()); - - //Setup context menu - setupContextMenu(); + // Signals that require a refresh all + connect(Core(), SIGNAL(refreshAll()), this, SLOT(refreshView())); + connect(Core(), SIGNAL(commentsChanged()), this, SLOT(refreshView())); + connect(Core(), SIGNAL(functionRenamed(QString, QString)), this, SLOT(refreshView())); + connect(Core(), SIGNAL(flagsChanged()), this, SLOT(refreshView())); + connect(Core(), SIGNAL(varsChanged()), this, SLOT(refreshView())); + connect(Core(), SIGNAL(instructionChanged(RVA)), this, SLOT(refreshView())); + connect(Config(), SIGNAL(colorsUpdated()), this, SLOT(colorsUpdatedSlot())); + connect(Config(), SIGNAL(fontsUpdated()), this, SLOT(fontsUpdatedSlot())); + connect(Core(), SIGNAL(seekChanged(RVA)), this, SLOT(onSeekChanged(RVA))); // Space to switch to disassembly QShortcut *disassemblyShortcut = new QShortcut(QKeySequence(Qt::Key_Space), this); @@ -72,701 +37,132 @@ DisassemblerGraphView::DisassemblerGraphView(QWidget *parent) Core()->setMemoryWidgetPriority(CutterCore::MemoryWidgetType::Disassembly); Core()->triggerRaisePrioritizedMemoryWidget(); }); + // ESC for previous + QShortcut *shortcut_escape = new QShortcut(QKeySequence(Qt::Key_Escape), this); + shortcut_escape->setContext(Qt::WidgetShortcut); + connect(shortcut_escape, SIGNAL(activated()), this, SLOT(seekPrev())); - //Connect to bridge - connect(Core(), SIGNAL(refreshAll()), this, SLOT(refreshView())); - connect(Core(), SIGNAL(seekChanged(RVA)), this, SLOT(onSeekChanged(RVA))); - connect(Core(), SIGNAL(commentsChanged()), this, SLOT(refreshView())); - connect(Core(), SIGNAL(functionRenamed(QString, QString)), this, SLOT(refreshView())); - connect(Core(), SIGNAL(flagsChanged()), this, SLOT(refreshView())); - connect(Core(), SIGNAL(varsChanged()), this, SLOT(refreshView())); - connect(Core(), SIGNAL(instructionChanged(RVA)), this, SLOT(refreshView())); + QShortcut *shortcut_zoom_in = new QShortcut(QKeySequence(Qt::Key_Plus), this); + shortcut_zoom_in->setContext(Qt::WidgetShortcut); + connect(shortcut_zoom_in, SIGNAL(activated()), this, SLOT(zoomIn())); - //connect(Bridge::getBridge(), SIGNAL(loadGraph(BridgeCFGraphList*, duint)), this, SLOT(loadGraphSlot(BridgeCFGraphList*, duint))); - //connect(Bridge::getBridge(), SIGNAL(graphAt(duint)), this, SLOT(graphAtSlot(duint))); - //connect(Bridge::getBridge(), SIGNAL(updateGraph()), this, SLOT(updateGraphSlot())); - //connect(Bridge::getBridge(), SIGNAL(selectionGraphGet(SELECTIONDATA*)), this, SLOT(selectionGetSlot(SELECTIONDATA*))); - //connect(Bridge::getBridge(), SIGNAL(disassembleAt(dsint, dsint)), this, SLOT(disassembleAtSlot(dsint, dsint))); - //connect(Bridge::getBridge(), SIGNAL(focusGraph()), this, SLOT(setFocus())); + QShortcut *shortcut_zoom_out = new QShortcut(QKeySequence(Qt::Key_Minus), this); + shortcut_zoom_in->setContext(Qt::WidgetShortcut); + connect(shortcut_zoom_out, SIGNAL(activated()), this, SLOT(zoomOut())); - //Connect to config - connect(Config(), SIGNAL(colorsUpdated()), this, SLOT(colorsUpdatedSlot())); - connect(Config(), SIGNAL(fontsUpdated()), this, SLOT(fontsUpdatedSlot())); - //connect(Config(), SIGNAL(shortcutsUpdated()), this, SLOT(shortcutsUpdatedSlot())); - //connect(Config(), SIGNAL(tokenizerConfigUpdated()), this, SLOT(tokenizerConfigUpdatedSlot())); + QShortcut *shortcut_take_true = new QShortcut(QKeySequence(Qt::Key_T), this); + shortcut_take_true->setContext(Qt::WidgetShortcut); + connect(shortcut_take_true, SIGNAL(activated()), this, SLOT(takeTrue())); + QShortcut *shortcut_take_false = new QShortcut(QKeySequence(Qt::Key_F), this); + shortcut_take_false->setContext(Qt::WidgetShortcut); + connect(shortcut_take_false, SIGNAL(activated()), this, SLOT(takeFalse())); + initFont(); colorsUpdatedSlot(); } -DisassemblerGraphView::~DisassemblerGraphView() +void DisassemblerGraphView::refreshView() { - delete this->highlight_token; + initFont(); + loadCurrentGraph(); + this->viewport()->update(); } -void DisassemblerGraphView::initFont() +void DisassemblerGraphView::loadCurrentGraph() { - setFont(Config()->getFont()); - QFontMetricsF metrics(this->font()); - this->baseline = int(metrics.ascent()); - this->charWidth = metrics.width('X'); - this->charHeight = metrics.height(); - this->charOffset = 0; - if(mFontMetrics) - delete mFontMetrics; - mFontMetrics = new CachedFontMetrics(this, font()); -} + QJsonDocument functionsDoc = Core()->cmdj("agj"); + QJsonArray functions = functionsDoc.array(); -void DisassemblerGraphView::adjustSize(int width, int height) -{ - //Recompute size information - this->renderWidth = this->width; - this->renderHeight = this->height; - this->renderXOfs = 0; - this->renderYOfs = 0; - if(this->renderWidth < width) + disassembly_blocks.clear(); + this->blocks.clear(); + + Analysis anal; + anal.ready = true; + + QJsonValue funcRef = functions.first(); + QJsonObject func = funcRef.toObject(); + Function f; + f.ready = true; + f.entry = func["offset"].toVariant().toULongLong(); + + QString windowTitle = tr("Graph"); + QString funcName = func["name"].toString().trimmed(); + if (!funcName.isEmpty()) { - this->renderXOfs = (width - this->renderWidth) / 2; - this->renderWidth = width; + windowTitle += " (" + funcName + ")"; } - if(this->renderHeight < height) - { - this->renderYOfs = (height - this->renderHeight) / 2; - this->renderHeight = height; - } - //Update scroll bar information - this->horizontalScrollBar()->setPageStep(width); - this->horizontalScrollBar()->setRange(0, this->renderWidth - width); - this->verticalScrollBar()->setPageStep(height); - this->verticalScrollBar()->setRange(0, this->renderHeight - height); -} + this->parentWidget()->setWindowTitle(windowTitle); -void DisassemblerGraphView::resizeEvent(QResizeEvent* event) -{ - adjustSize(event->size().width(), event->size().height()); -} + RVA entry = func["offset"].toVariant().toULongLong(); -duint DisassemblerGraphView::get_cursor_pos() -{ - if(this->cur_instr == 0) - return this->function; - return this->cur_instr; -} + setEntry(entry); + for (QJsonValueRef blockRef : func["blocks"].toArray()) { + QJsonObject block = blockRef.toObject(); + RVA block_entry = block["offset"].toVariant().toULongLong(); + RVA block_fail = block["fail"].toVariant().toULongLong(); + RVA block_jump = block["jump"].toVariant().toULongLong(); -void DisassemblerGraphView::set_cursor_pos(duint addr) -{ - Q_UNUSED(addr); - if(!this->navigate(addr)) - { - //TODO: show in hex editor? - } -} - -std::tuple DisassemblerGraphView::get_selection_range() -{ - return std::make_tuple(get_cursor_pos(), get_cursor_pos()); -} - -void DisassemblerGraphView::set_selection_range(std::tuple range) -{ - this->set_cursor_pos(std::get<0>(range)); -} - -void DisassemblerGraphView::copy_address() -{ - QClipboard* clipboard = QApplication::clipboard(); - clipboard->clear(); - QMimeData mime; - mime.setText(QString().sprintf("0x%llx", this->get_cursor_pos())); - clipboard->setMimeData(&mime); -} - -void DisassemblerGraphView::paintNormal(QPainter & p, QRect & viewportRect, int xofs, int yofs) -{ - //Translate the painter - //auto dbgfunctions = DbgFunctions(); - QPoint translation(this->renderXOfs - xofs, this->renderYOfs - yofs); - p.translate(translation); - viewportRect.translate(-translation.x(), -translation.y()); - - //Render each node - for(auto & blockIt : this->blocks) - { - DisassemblerBlock & block = blockIt.second; - bool blockSelected = false; - for(const Instr & instr : block.block.instrs) + DisassemblyBlock db; + GraphBlock gb; + gb.entry = block_entry; + db.entry = block_entry; + db.true_path = RVA_INVALID; + db.false_path = RVA_INVALID; + if(block_fail) { - if(instr.addr == this->cur_instr) + db.false_path = block_fail; + gb.exits.push_back(block_fail); + } + if(block_jump) + { + if(block_fail) { - blockSelected = true; + db.true_path = block_jump; } + gb.exits.push_back(block_jump); } - - //Ignore blocks that are not in view - if(viewportRect.intersects(QRect(block.x + this->charWidth, block.y + this->charWidth, - block.width - (2 * this->charWidth), block.height - (2 * this->charWidth)))) - { - //Render shadow - p.setPen(QColor(0, 0, 0, 0)); - if(block.block.terminal) - p.setBrush(retShadowColor); - else if(block.block.indirectcall) - p.setBrush(indirectcallShadowColor); - else - p.setBrush(QColor(0, 0, 0, 128)); - p.drawRect(block.x + this->charWidth + 4, block.y + this->charWidth + 4, - block.width - (4 + 2 * this->charWidth), block.height - (4 + 2 * this->charWidth)); - - //Render node background - p.setPen(graphNodeColor); - p.setBrush(disassemblyBackgroundColor); - p.drawRect(block.x + this->charWidth, block.y + this->charWidth, - block.width - (4 + 2 * this->charWidth), block.height - (4 + 2 * this->charWidth)); - - //Print current instruction background - if(this->cur_instr != 0) - { - int y = block.y + (2 * this->charWidth) + (int(block.block.header_text.lines.size()) * this->charHeight); - for(Instr & instr : block.block.instrs) - { - if(y > viewportRect.y() - int(instr.text.lines.size()) * this->charHeight && y < viewportRect.bottom()) - { - auto selected = instr.addr == this->cur_instr; - //auto traceCount = dbgfunctions->GetTraceRecordHitCount(instr.addr); - auto traceCount = 0; - - if(selected && traceCount) - { - p.fillRect(QRect(block.x + this->charWidth + 3, y, block.width - (10 + 2 * this->charWidth), - int(instr.text.lines.size()) * this->charHeight), disassemblyTracedSelectionColor); - } - else if(selected) - { - p.fillRect(QRect(block.x + this->charWidth + 3, y, block.width - (10 + 2 * this->charWidth), - int(instr.text.lines.size()) * this->charHeight), disassemblySelectionColor); - } - else if(traceCount) - { - // Color depending on how often a sequence of code is executed - int exponent = 1; - while(traceCount >>= 1) //log2(traceCount) - exponent++; - int colorDiff = (exponent * exponent) / 2; - - // If the user has a light trace background color, substract - if(disassemblyTracedColor.blue() > 160) - colorDiff *= -1; - - p.fillRect(QRect(block.x + this->charWidth + 3, y, block.width - (10 + 2 * this->charWidth), int(instr.text.lines.size()) * this->charHeight), - QColor(disassemblyTracedColor.red(), - disassemblyTracedColor.green(), - std::max(0, std::min(256, disassemblyTracedColor.blue() + colorDiff)))); - } - } - y += int(instr.text.lines.size()) * this->charHeight; - } - } - - //Render node text - auto x = block.x + (2 * this->charWidth); - auto y = block.y + (2 * this->charWidth); - for(auto & line : block.block.header_text.lines) - { - if(y > viewportRect.y() - this->charHeight && y < viewportRect.bottom()) - { - RichTextPainter::paintRichText(&p, x, y, block.width, this->charHeight, 0, line, mFontMetrics); - } - y += this->charHeight; - } - - for(Instr & instr : block.block.instrs) - { - for(auto & line : instr.text.lines) - { - if(y > viewportRect.y() - this->charHeight && y < viewportRect.bottom()) - { - int rectSize = qRound(this->charWidth); - if(rectSize % 2) - rectSize++; - - // Assume charWidth <= charHeight - QRectF bpRect(x - rectSize / 3.0, y + (this->charHeight - rectSize) / 2.0, rectSize, rectSize); - - //bool isbp = DbgGetBpxTypeAt(instr.addr) != bp_none; - //bool isbpdisabled = DbgIsBpDisabled(instr.addr); - bool isbp = false; - bool isbpdisabled = true; - bool iscip = instr.addr == mCip; - - if(isbp || isbpdisabled) - { - if(iscip) - { - // Left half is cip - bpRect.setWidth(bpRect.width() / 2); - p.fillRect(bpRect, mCipColor); - - // Right half is breakpoint - bpRect.translate(bpRect.width(), 0); - } - - p.fillRect(bpRect, isbp ? mBreakpointColor : mDisabledBreakpointColor); - } - else if(iscip) - p.fillRect(bpRect, mCipColor); - - RichTextPainter::paintRichText(&p, x + this->charWidth, y, block.width - this->charWidth, this->charHeight, 0, line, mFontMetrics); - } - y += this->charHeight; - } + for (QJsonValueRef opRef : block["ops"].toArray()) { + QJsonObject op = opRef.toObject(); + Instr i; + i.addr = op["offset"].toVariant().toULongLong(); + // Skip last byte, otherwise it will overlap with next instruction + i.size = op["size"].toVariant().toULongLong() - 1; + RichTextPainter::List richText; + Colors::colorizeAssembly(richText, op["opcode"].toString(), op["type_num"].toVariant().toULongLong()); + if (op["comment"].toString().length()) { + RichTextPainter::CustomRichText_t comment; + comment.text = QString(" ; %1").arg(QByteArray::fromBase64(op["comment"].toString().toLocal8Bit()).data()); + comment.textColor = mCommentColor; + comment.flags = RichTextPainter::FlagColor; + richText.insert(richText.end(), comment); } + i.text = Text(richText); + db.instrs.push_back(i); } + disassembly_blocks[db.entry] = db; + prepareGraphNode(gb); + f.blocks.push_back(db); - // Render edges - for(DisassemblerEdge & edge : block.edges) - { - QPen pen(edge.color); - if(blockSelected) - pen.setStyle(Qt::DashLine); - p.setPen(pen); - p.setBrush(edge.color); - p.drawPolyline(edge.polyline); - pen.setStyle(Qt::SolidLine); - p.setPen(pen); - p.drawConvexPolygon(edge.arrow); - } - } -} - -void DisassemblerGraphView::paintOverview(QPainter & p, QRect & viewportRect, int xofs, int yofs) -{ - // Scale and translate painter - //auto dbgfunctions = DbgFunctions(); - qreal sx = qreal(viewportRect.width()) / qreal(this->renderWidth); - qreal sy = qreal(viewportRect.height()) / qreal(this->renderHeight); - qreal s = qMin(sx, sy); - this->overviewScale = s; - if(sx < sy) - { - this->overviewXOfs = this->renderXOfs * s; - this->overviewYOfs = this->renderYOfs * s + (qreal(this->renderHeight) * sy - qreal(this->renderHeight) * s) / 2; - } - else if(sy < sx) - { - this->overviewXOfs = this->renderXOfs * s + (qreal(this->renderWidth) * sx - qreal(this->renderWidth) * s) / 2; - this->overviewYOfs = this->renderYOfs * s; - } - else - { - this->overviewXOfs = this->renderXOfs; - this->overviewYOfs = this->renderYOfs; - } - p.translate(this->overviewXOfs, this->overviewYOfs); - p.scale(s, s); - - // Scaled pen - QPen pen; - qreal penWidth = 1.0 / s; - pen.setWidthF(penWidth); - - //Render each node - duint cipBlock = 0; - auto found = currentBlockMap.find(mCip); - if(found != currentBlockMap.end()) - cipBlock = found->second; - for(auto & blockIt : this->blocks) - { - DisassemblerBlock & block = blockIt.second; - - // Render edges - for(DisassemblerEdge & edge : block.edges) - { - pen.setColor(edge.color); - p.setPen(pen); - p.setBrush(edge.color); - p.drawPolyline(edge.polyline); - p.drawConvexPolygon(edge.arrow); - } - - //Get block metadata - //auto traceCount = dbgfunctions->GetTraceRecordHitCount(block.block.entry); - auto traceCount = 0; - auto isCip = block.block.entry == cipBlock; - - //Render shadow - p.setPen(QColor(0, 0, 0, 0)); - if((isCip || traceCount) && block.block.terminal) - p.setBrush(retShadowColor); - else if((isCip || traceCount) && block.block.indirectcall) - p.setBrush(indirectcallShadowColor); - else if(isCip) - p.setBrush(QColor(0, 0, 0, 0)); - else - p.setBrush(QColor(0, 0, 0, 128)); - p.drawRect(block.x + this->charWidth + 4, block.y + this->charWidth + 4, - block.width - (4 + 2 * this->charWidth), block.height - (4 + 2 * this->charWidth)); - - //Render node background - pen.setColor(graphNodeColor); - p.setPen(pen); - if(isCip) - p.setBrush(mCipColor); - else if(traceCount) - { - // Color depending on how often a sequence of code is executed - int exponent = 1; - while(traceCount >>= 1) //log2(traceCount) - exponent++; - int colorDiff = (exponent * exponent) / 2; - - // If the user has a light trace background color, substract - if(disassemblyTracedColor.blue() > 160) - colorDiff *= -1; - - p.setBrush(QColor(disassemblyTracedColor.red(), - disassemblyTracedColor.green(), - std::max(0, std::min(256, disassemblyTracedColor.blue() + colorDiff)))); - } - else if(block.block.terminal) - p.setBrush(retShadowColor); - else if(block.block.indirectcall) - p.setBrush(indirectcallShadowColor); - else - p.setBrush(disassemblyBackgroundColor); - p.drawRect(block.x + this->charWidth, block.y + this->charWidth, - block.width - (4 + 2 * this->charWidth), block.height - (4 + 2 * this->charWidth)); + addBlock(gb); } - // Draw viewport selection - if(s < 1.0) + anal.functions[f.entry] = f; + anal.status = "Ready."; + anal.entry = f.entry; + + if(func["blocks"].toArray().size() > 0) { - QPoint translation(this->renderXOfs - xofs, this->renderYOfs - yofs); - viewportRect.translate(-translation.x(), -translation.y()); - p.setPen(QPen(graphNodeColor, penWidth, Qt::DotLine)); - p.setBrush(Qt::transparent); - p.drawRect(viewportRect); - } -} - -void DisassemblerGraphView::paintEvent(QPaintEvent* event) -{ - Q_UNUSED(event); - QPainter p(this->viewport()); - p.setFont(this->font()); - - int xofs = this->horizontalScrollBar()->value(); - int yofs = this->verticalScrollBar()->value(); - - //Render background - QRect viewportRect(this->viewport()->rect().topLeft(), this->viewport()->rect().bottomRight() - QPoint(1, 1)); - p.setBrush(backgroundColor); - p.drawRect(viewportRect); - p.setBrush(Qt::black); - - /*if(!this->ready || !DbgIsDebugging()) - { - p.setPen(graphNodeColor); - p.drawText(viewportRect, Qt::AlignCenter | Qt::AlignVCenter, tr("Use Graph command or menu action to draw control flow graph here...")); - return; - }*/ - - if(drawOverview) - paintOverview(p, viewportRect, xofs, yofs); - else - paintNormal(p, viewportRect, xofs, yofs); - - if(saveGraph) - { - saveGraph = false; - QString path = QFileDialog::getSaveFileName(this, tr("Save as image"), "", tr("PNG file (*.png);;BMP file (*.bmp)")); - if(path.isEmpty()) - return; - - // expand to full render Rectangle - this->viewport()->resize(this->renderWidth, this->renderHeight);//OK - - //save viewport to image - QRect completeRenderRect = QRect(0, 0, this->renderWidth, this->renderHeight); - QImage img(completeRenderRect.size(), QImage::Format_ARGB32); - QPainter painter(&img); - this->viewport()->render(&painter); - img.save(path); - - //restore changes made to viewport for full render saving - this->viewport()->resize(this->viewport()->width(), this->viewport()->height()); - } -} - -bool DisassemblerGraphView::isMouseEventInBlock(QMouseEvent* event) -{ - //Convert coordinates to system used in blocks - int xofs = this->horizontalScrollBar()->value(); - int yofs = this->verticalScrollBar()->value(); - int x = event->x() + xofs - this->renderXOfs; - int y = event->y() + yofs - this->renderYOfs; - - // Check each block for hits - for(auto & blockIt : this->blocks) - { - DisassemblerBlock & block = blockIt.second; - //Compute coordinate relative to text area in block - int blockx = x - (block.x + (2 * this->charWidth)); - int blocky = y - (block.y + (2 * this->charWidth)); - //Check to see if click is within bounds of block - if((blockx < 0) || (blockx > (block.width - 4 * this->charWidth))) - continue; - if((blocky < 0) || (blocky > (block.height - 4 * this->charWidth))) - continue; - return true; - } - return false; -} - -duint DisassemblerGraphView::getInstrForMouseEvent(QMouseEvent* event) -{ - //Convert coordinates to system used in blocks - int xofs = this->horizontalScrollBar()->value(); - int yofs = this->verticalScrollBar()->value(); - int x = event->x() + xofs - this->renderXOfs; - int y = event->y() + yofs - this->renderYOfs; - - //Check each block for hits - for(auto & blockIt : this->blocks) - { - DisassemblerBlock & block = blockIt.second; - //Compute coordinate relative to text area in block - int blockx = x - (block.x + (2 * this->charWidth)); - int blocky = y - (block.y + (2 * this->charWidth)); - //Check to see if click is within bounds of block - if((blockx < 0) || (blockx > (block.width - 4 * this->charWidth))) - continue; - if((blocky < 0) || (blocky > (block.height - 4 * this->charWidth))) - continue; - //Compute row within text - int row = int(blocky / this->charHeight); - //Determine instruction for this row - int cur_row = int(block.block.header_text.lines.size()); - if(row < cur_row) - return block.block.entry; - for(Instr & instr : block.block.instrs) - { - if(row < cur_row + int(instr.text.lines.size())) - return instr.addr; - cur_row += int(instr.text.lines.size()); - } - } - return 0; -} - -bool DisassemblerGraphView::getTokenForMouseEvent(QMouseEvent* event, Token & tokenOut) -{ - Q_UNUSED(event); - Q_UNUSED(tokenOut); - /* TODO - //Convert coordinates to system used in blocks - int xofs = this->horizontalScrollBar()->value(); - int yofs = this->verticalScrollBar()->value(); - int x = event->x() + xofs - this->renderXOfs; - int y = event->y() + yofs - this->renderYOfs; - - //Check each block for hits - for(auto & blockIt : this->blocks) - { - DisassemblerBlock & block = blockIt.second; - //Compute coordinate relative to text area in block - int blockx = x - (block.x + (2 * this->charWidth)); - int blocky = y - (block.y + (2 * this->charWidth)); - //Check to see if click is within bounds of block - if((blockx < 0) || (blockx > (block.width - 4 * this->charWidth))) - continue; - if((blocky < 0) || (blocky > (block.height - 4 * this->charWidth))) - continue; - //Compute row and column within text - int col = int(blockx / this->charWidth); - int row = int(blocky / this->charHeight); - //Check tokens to see if one was clicked - int cur_row = 0; - for(auto & line : block.block.header_text.tokens) - { - if(cur_row == row) - { - for(Token & token : line) - { - if((col >= token.start) && (col < (token.start + token.length))) - { - //Clicked on a token - tokenOut = token; - return true; - } - } - } - cur_row += 1; - } - for(Instr & instr : block.block.instrs) - { - for(auto & line : instr.text.tokens) - { - if(cur_row == row) - { - for(Token & token : line) - { - if((col >= token.start) && (col < (token.start + token.length))) - { - //Clicked on a token - tokenOut = token; - return true; - } - } - } - cur_row += 1; - } - } - }*/ - return false; -} - -bool DisassemblerGraphView::find_instr(duint addr, Instr & instrOut) -{ - for(auto & blockIt : this->blocks) - for(Instr & instr : blockIt.second.block.instrs) - if(instr.addr == addr) - { - instrOut = instr; - return true; - } - return false; -} - -void DisassemblerGraphView::mousePressEvent(QMouseEvent* event) -{ - //if(!DbgIsDebugging()) - // return; - if(drawOverview) - { - if(event->button() == Qt::LeftButton) - { - //Enter scrolling mode - this->scroll_base_x = event->x(); - this->scroll_base_y = event->y(); - this->scroll_mode = true; - this->setCursor(Qt::ClosedHandCursor); - this->viewport()->grabMouse(); - - //Scroll to the cursor - this->horizontalScrollBar()->setValue(((event->x() - this->overviewXOfs) / this->overviewScale) - this->viewport()->width() / 2); - this->verticalScrollBar()->setValue(((event->y() - this->overviewYOfs) / this->overviewScale) - this->viewport()->height() / 2); - } - /*else if(event->button() == Qt::RightButton) - { - QMenu wMenu(this); - mMenuBuilder->build(&wMenu); - wMenu.exec(event->globalPos()); //execute context menu - }*/ - } - else if((event->button() == Qt::LeftButton || event->button() == Qt::RightButton) && this->isMouseEventInBlock(event)) - { - //Check for click on a token and highlight it - Token token; - delete this->highlight_token; - if(this->getTokenForMouseEvent(event, token)) - this->highlight_token = HighlightToken::fromToken(token); - else - this->highlight_token = nullptr; - - //Update current instruction - duint instr = this->getInstrForMouseEvent(event); - if(instr != 0) { - this->cur_instr = instr; - emit currentInstructionUpdated(instr); - } - + computeGraph(entry); this->viewport()->update(); - - if(event->button() == Qt::RightButton) - { - mMenu->exec(event->globalPos()); //execute context menu - } - } - else if(event->button() == Qt::LeftButton) - { - //Left click outside any block, enter scrolling mode - this->scroll_base_x = event->x(); - this->scroll_base_y = event->y(); - this->scroll_mode = true; - this->setCursor(Qt::ClosedHandCursor); - this->viewport()->grabMouse(); - } - else if(event->button() == Qt::RightButton) - { - /*//Right click outside of block - QMenu wMenu(this); - mMenuBuilder->build(&wMenu); - wMenu.exec(event->globalPos()); //execute context menu - */ } } -void DisassemblerGraphView::mouseMoveEvent(QMouseEvent* event) -{ - if(this->scroll_mode) - { - int x_delta = this->scroll_base_x - event->x(); - int y_delta = this->scroll_base_y - event->y(); - if(drawOverview) - { - x_delta = -x_delta / this->overviewScale; - y_delta = -y_delta / this->overviewScale; - } - this->scroll_base_x = event->x(); - this->scroll_base_y = event->y(); - this->horizontalScrollBar()->setValue(this->horizontalScrollBar()->value() + x_delta); - this->verticalScrollBar()->setValue(this->verticalScrollBar()->value() + y_delta); - } -} - -void DisassemblerGraphView::mouseReleaseEvent(QMouseEvent* event) -{ - if(event->button() == Qt::ForwardButton) - gotoNextSlot(); - else if(event->button() == Qt::BackButton) - gotoPreviousSlot(); - - if(event->button() != Qt::LeftButton) - return; - - if(this->scroll_mode) - { - this->scroll_mode = false; - this->setCursor(Qt::ArrowCursor); - this->viewport()->releaseMouse(); - } -} - -void DisassemblerGraphView::mouseDoubleClickEvent(QMouseEvent* event) -{ - Q_UNUSED(event); - if(drawOverview) - { - toggleOverviewSlot(); - } - else - { - duint instr = this->getInstrForMouseEvent(event); - //DbgCmdExec(QString("graph dis.branchdest(%1), silent").arg(ToPtrString(instr)).toUtf8().constData()); - QList refs = Core()->getXRefs(instr, false, false); - if (refs.length()) { - Core()->seek(refs.at(0).to); - } - if (refs.length() > 1) { - qWarning() << "Too many references here. Weird behaviour expected."; - } - } -} - -void DisassemblerGraphView::prepareGraphNode(DisassemblerBlock & block) +void DisassemblerGraphView::prepareGraphNode(GraphBlock &block) { + DisassemblyBlock &db = disassembly_blocks[block.entry]; int width = 0; int height = 0; - for(auto & line : block.block.header_text.lines) + for(auto & line : db.header_text.lines) { int lw = 0; for(auto & part : line) @@ -775,7 +171,7 @@ void DisassemblerGraphView::prepareGraphNode(DisassemblerBlock & block) width = lw; height += 1; } - for(Instr & instr : block.block.instrs) + for(Instr & instr : db.instrs) { for(auto & line : instr.text.lines) { @@ -792,908 +188,187 @@ void DisassemblerGraphView::prepareGraphNode(DisassemblerBlock & block) block.height = (height * this->charHeight) + extra; } -void DisassemblerGraphView::adjustGraphLayout(DisassemblerBlock & block, int col, int row) + +void DisassemblerGraphView::initFont() { - block.col += col; - block.row += row; - for(duint edge : block.new_exits) - this->adjustGraphLayout(this->blocks[edge], col, row); + setFont(Config()->getFont()); + QFontMetricsF metrics(this->font()); + this->baseline = int(metrics.ascent()); + this->charWidth = metrics.width('X'); + this->charHeight = metrics.height(); + this->charOffset = 0; + if(mFontMetrics) + delete mFontMetrics; + mFontMetrics = new CachedFontMetrics(this, font()); } -void DisassemblerGraphView::computeGraphLayout(DisassemblerBlock & block) +void DisassemblerGraphView::drawBlock(QPainter & p, GraphView::GraphBlock &block) { - //Compute child node layouts and arrange them horizontally - int col = 0; - int row_count = 1; - int childColumn = 0; - bool singleChild = block.new_exits.size() == 1; - for(size_t i = 0; i < block.new_exits.size(); i++) + p.setPen(Qt::black); + p.setBrush(Qt::gray); + p.drawRect(block.x, block.y, block.width, block.height); + + + // Render node + DisassemblyBlock &db = disassembly_blocks[block.entry]; + bool block_selected = false; + RVA selected_instruction = RVA_INVALID; + + // Figure out if the current block is selected + for(const Instr & instr : db.instrs) { - duint edge = block.new_exits[i]; - this->computeGraphLayout(this->blocks[edge]); - if((this->blocks[edge].row_count + 1) > row_count) - row_count = this->blocks[edge].row_count + 1; - childColumn = this->blocks[edge].col; + RVA addr = Core()->getOffset(); + if((instr.addr <= addr) && (addr <= instr.addr+instr.size)) + { + block_selected = true; + selected_instruction = instr.addr; + } + // TODO: L219 } - if(this->layoutType != LayoutType::Wide && block.new_exits.size() == 2) + p.setPen(QColor(0, 0, 0, 0)); + if(db.terminal) { - DisassemblerBlock & left = this->blocks[block.new_exits[0]]; - DisassemblerBlock & right = this->blocks[block.new_exits[1]]; - if(left.new_exits.size() == 0) - { - left.col = right.col - 2; - int add = left.col < 0 ? - left.col : 0; - this->adjustGraphLayout(right, add, 1); - this->adjustGraphLayout(left, add, 1); - col = right.col_count + add; - } - else if(right.new_exits.size() == 0) - { - this->adjustGraphLayout(left, 0, 1); - this->adjustGraphLayout(right, left.col + 2, 1); - col = std::max(left.col_count, right.col + 2); - } - else - { - this->adjustGraphLayout(left, 0, 1); - this->adjustGraphLayout(right, left.col_count, 1); - col = left.col_count + right.col_count; - } + p.setBrush(retShadowColor); + } else if(db.indirectcall) { + p.setBrush(indirectcallShadowColor); + } else { + p.setBrush(QColor(0, 0, 0, 128)); + } - block.col_count = std::max(2, col); - if(layoutType == LayoutType::Medium) - block.col = (left.col + right.col) / 2; - else - block.col = singleChild ? childColumn : (col - 2) / 2; + p.drawRect(block.x + 4, block.y + 4, + block.width + 4, block.height + 4); + p.setPen(graphNodeColor); + + if(block_selected) + { + p.setBrush(disassemblySelectedBackgroundColor); + } else { + p.setBrush(disassemblyBackgroundColor); + } + + p.drawRect(block.x, block.y, + block.width, block.height); + + // Draw different background for selected instruction + if(selected_instruction != RVA_INVALID) + { + int y = block.y + (2 * this->charWidth) + (db.header_text.lines.size() * this->charHeight); + for(Instr & instr : db.instrs) + { + auto selected = instr.addr == selected_instruction; + //auto traceCount = dbgfunctions->GetTraceRecordHitCount(instr.addr); + auto traceCount = 0; + if(selected && traceCount) + { + p.fillRect(QRect(block.x + this->charWidth, y, block.width - (10 + 2 * this->charWidth), + int(instr.text.lines.size()) * this->charHeight), disassemblyTracedSelectionColor); + } + else if(selected) + { + p.fillRect(QRect(block.x + this->charWidth, y, block.width - (10 + 2 * this->charWidth), + int(instr.text.lines.size()) * this->charHeight), disassemblySelectionColor); + } + else if(traceCount) + { + // Color depending on how often a sequence of code is executed + int exponent = 1; + while(traceCount >>= 1) //log2(traceCount) + exponent++; + int colorDiff = (exponent * exponent) / 2; + + // If the user has a light trace background color, substract + if(disassemblyTracedColor.blue() > 160) + colorDiff *= -1; + + p.fillRect(QRect(block.x + this->charWidth, y, block.width - (10 + 2 * this->charWidth), int(instr.text.lines.size()) * this->charHeight), + QColor(disassemblyTracedColor.red(), + disassemblyTracedColor.green(), + std::max(0, std::min(256, disassemblyTracedColor.blue() + colorDiff)))); + } + y += int(instr.text.lines.size()) * this->charHeight; + } + } + + + // Render node text + auto x = block.x + (2 * this->charWidth); + int y = block.y + (2 * this->charWidth); + for(auto & line : db.header_text.lines) + { + RichTextPainter::paintRichText(&p, x, y, block.width, this->charHeight, 0, line, mFontMetrics); + y += this->charHeight; + } + for(Instr & instr : db.instrs) + { + for(auto & line : instr.text.lines) + { + int rectSize = qRound(this->charWidth); + if(rectSize % 2) + { + rectSize++; + } + // Assume charWidth <= charHeight + QRectF bpRect(x - rectSize / 3.0, y + (this->charHeight - rectSize) / 2.0, rectSize, rectSize); + + // TODO: Breakpoint/Cip stuff + + RichTextPainter::paintRichText(&p, x + this->charWidth, y, block.width - this->charWidth, this->charHeight, 0, line, mFontMetrics); + y += this->charHeight; + + } + } +} + +GraphView::EdgeConfiguration DisassemblerGraphView::edgeConfiguration(GraphView::GraphBlock &from, GraphView::GraphBlock *to) +{ + EdgeConfiguration ec; + DisassemblyBlock &db = disassembly_blocks[from.entry]; + if(to->entry == db.true_path) + { + ec.color = brtrueColor; + } + else if(to->entry == db.false_path) + { + ec.color = brfalseColor; } else { - for(duint edge : block.new_exits) - { - this->adjustGraphLayout(this->blocks[edge], col, 1); - col += this->blocks[edge].col_count; - } - if(col >= 2) - { - //Place this node centered over the child nodes - block.col = singleChild ? childColumn : (col - 2) / 2; - block.col_count = col; - } - else - { - //No child nodes, set single node's width (nodes are 2 columns wide to allow - //centering over a branch) - block.col = 0; - block.col_count = 2; - } + ec.color = jmpColor; } - block.row = 0; - block.row_count = row_count; + ec.start_arrow = false; + ec.end_arrow = true; + return ec; } -bool DisassemblerGraphView::isEdgeMarked(EdgesVector & edges, int row, int col, int index) +RVA DisassemblerGraphView::getInstrForMouseEvent(GraphBlock &block, QPoint* point) { - if(index >= int(edges[row][col].size())) - return false; - return edges[row][col][index]; -} - -void DisassemblerGraphView::markEdge(EdgesVector & edges, int row, int col, int index, bool used) -{ - while(int(edges[row][col].size()) <= index) - edges[row][col].push_back(false); - edges[row][col][index] = used; -} - -int DisassemblerGraphView::findHorizEdgeIndex(EdgesVector & edges, int row, int min_col, int max_col) -{ - //Find a valid index - int i = 0; - while(true) + DisassemblyBlock &db = disassembly_blocks[block.entry]; + int mouse_row = ((point->y()-(2*this->charWidth)) / this->charHeight); + int cur_row = db.header_text.lines.size(); + if (mouse_row < cur_row) { - bool valid = true; - for(int col = min_col; col < max_col + 1; col++) - if(isEdgeMarked(edges, row, col, i)) - { - valid = false; - break; - } - if(valid) - break; - i++; + return db.entry; } - //Mark chosen index as used - for(int col = min_col; col < max_col + 1; col++) - this->markEdge(edges, row, col, i); - return i; -} - -int DisassemblerGraphView::findVertEdgeIndex(EdgesVector & edges, int col, int min_row, int max_row) -{ - //Find a valid index - int i = 0; - while(true) + for(Instr & instr : db.instrs) { - bool valid = true; - for(int row = min_row; row < max_row + 1; row++) - if(isEdgeMarked(edges, row, col, i)) - { - valid = false; - break; - } - if(valid) - break; - i++; - } - - //Mark chosen index as used - for(int row = min_row; row < max_row + 1; row++) - this->markEdge(edges, row, col, i); - return i; -} - -DisassemblerGraphView::DisassemblerEdge DisassemblerGraphView::routeEdge(EdgesVector & horiz_edges, EdgesVector & vert_edges, Matrix & edge_valid, DisassemblerBlock & start, DisassemblerBlock & end, QColor color) -{ - DisassemblerEdge edge; - edge.color = color; - edge.dest = &end; - - //Find edge index for initial outgoing line - int i = 0; - while(true) - { - if(!this->isEdgeMarked(vert_edges, start.row + 1, start.col + 1, i)) - break; - i += 1; - } - this->markEdge(vert_edges, start.row + 1, start.col + 1, i); - edge.addPoint(start.row + 1, start.col + 1); - edge.start_index = i; - bool horiz = false; - - //Find valid column for moving vertically to the target node - int min_row, max_row; - if(end.row < (start.row + 1)) - { - min_row = end.row; - max_row = start.row + 1; - } - else - { - min_row = start.row + 1; - max_row = end.row; - } - int col = start.col + 1; - if(min_row != max_row) - { - auto checkColumn = [min_row, max_row, &edge_valid](int column) + if(mouse_row < cur_row + (int)instr.text.lines.size()) { - if(column < 0 || column >= int(edge_valid[min_row].size())) - return false; - for(int row = min_row; row < max_row; row++) - { - if(!edge_valid[row][column]) - { - return false; - } - } - return true; - }; - - if(!checkColumn(col)) - { - if(checkColumn(end.col + 1)) - { - col = end.col + 1; - } - else - { - int ofs = 0; - while(true) - { - col = start.col + 1 - ofs; - if(checkColumn(col)) - { - break; - } - - col = start.col + 1 + ofs; - if(checkColumn(col)) - { - break; - } - - ofs += 1; - } - } + return instr.addr; } + cur_row += instr.text.lines.size(); } - - if(col != (start.col + 1)) - { - //Not in same column, need to generate a line for moving to the correct column - int min_col, max_col; - if(col < (start.col + 1)) - { - min_col = col; - max_col = start.col + 1; - } - else - { - min_col = start.col + 1; - max_col = col; - } - int index = this->findHorizEdgeIndex(horiz_edges, start.row + 1, min_col, max_col); - edge.addPoint(start.row + 1, col, index); - horiz = true; - } - - if(end.row != (start.row + 1)) - { - //Not in same row, need to generate a line for moving to the correct row - if(col == (start.col + 1)) - this->markEdge(vert_edges, start.row + 1, start.col + 1, i, false); - int index = this->findVertEdgeIndex(vert_edges, col, min_row, max_row); - if(col == (start.col + 1)) - edge.start_index = index; - edge.addPoint(end.row, col, index); - horiz = false; - } - - if(col != (end.col + 1)) - { - //Not in ending column, need to generate a line for moving to the correct column - int min_col, max_col; - if(col < (end.col + 1)) - { - min_col = col; - max_col = end.col + 1; - } - else - { - min_col = end.col + 1; - max_col = col; - } - int index = this->findHorizEdgeIndex(horiz_edges, end.row, min_col, max_col); - edge.addPoint(end.row, end.col + 1, index); - horiz = true; - } - - //If last line was horizontal, choose the ending edge index for the incoming edge - if(horiz) - { - int index = this->findVertEdgeIndex(vert_edges, end.col + 1, end.row, end.row); - edge.points[int(edge.points.size()) - 1].index = index; - } - - return edge; -} - -template -static void removeFromVec(std::vector & vec, T elem) -{ - vec.erase(std::remove(vec.begin(), vec.end(), elem), vec.end()); -} - -template -static void initVec(std::vector & vec, size_t size, T value) -{ - vec.resize(size); - for(size_t i = 0; i < size; i++) - vec[i] = value; -} - -void DisassemblerGraphView::renderFunction(Function & func) -{ - //Create render nodes - this->blocks.clear(); - for(Block & block : func.blocks) - { - this->blocks[block.entry] = DisassemblerBlock(block); - this->prepareGraphNode(this->blocks[block.entry]); - } - //puts("Create render nodes"); - - //Populate incoming lists - for(auto & blockIt : this->blocks) - { - DisassemblerBlock & block = blockIt.second; - for(auto & edge : block.block.exits) - this->blocks[edge].incoming.push_back(block.block.entry); - } - //puts("Populate incoming lists"); - - //Construct acyclic graph where each node is used as an edge exactly once - std::unordered_set visited; - visited.insert(func.entry); - std::queue queue; - queue.push(this->blocks[func.entry].block.entry); - std::vector blockOrder; - bool changed = true; - - while(changed) - { - changed = false; - - //First pick nodes that have single entry points - while(!queue.empty()) - { - DisassemblerBlock & block = this->blocks[queue.front()]; - queue.pop(); - blockOrder.push_back(block.block.entry); - - for(duint edge : block.block.exits) - { - if(visited.count(edge)) - continue; - - //If node has no more unseen incoming edges, add it to the graph layout now - if(int(this->blocks[edge].incoming.size()) == 1) - { - removeFromVec(this->blocks[edge].incoming, block.block.entry); - block.new_exits.push_back(edge); - queue.push(this->blocks[edge].block.entry); - visited.insert(edge); - changed = true; - } - else - { - removeFromVec(this->blocks[edge].incoming, block.block.entry); - } - } - } - - //No more nodes satisfy constraints, pick a node to continue constructing the graph - duint best = 0; - int best_edges; - duint best_parent; - for(auto & blockIt : this->blocks) - { - DisassemblerBlock & block = blockIt.second; - if(!visited.count(block.block.entry)) - continue; - for(duint edge : block.block.exits) - { - if(visited.count(edge)) - continue; - if((best == 0) || (int(this->blocks[edge].incoming.size()) < best_edges) || ( - (int(this->blocks[edge].incoming.size()) == best_edges) && (edge < best))) - { - best = edge; - best_edges = int(this->blocks[edge].incoming.size()); - best_parent = block.block.entry; - } - } - } - - if(best != 0) - { - DisassemblerBlock & best_parentb = this->blocks[best_parent]; - removeFromVec(this->blocks[best].incoming, best_parentb.block.entry); - best_parentb.new_exits.push_back(best); - visited.insert(best); - queue.push(best); - changed = true; - } - } - //puts("Construct acyclic graph where each node is used as an edge exactly once"); - - //Compute graph layout from bottom up - this->computeGraphLayout(this->blocks[func.entry]); - - //Optimize layout to be more compact - /*std::vector rowBlocks; - for(auto blockIt : this->blocks) - rowBlocks.push_back(blockIt.second); - std::sort(rowBlocks.begin(), rowBlocks.end(), [](DisassemblerBlock & a, DisassemblerBlock & b) - { - if(a.row < b.row) - return true; - if(a.row == b.row) - return a.col < b.col; - return false; - }); - std::vector> rowMap; - for(DisassemblerBlock & block : rowBlocks) - { - if(block.row == rowMap.size()) - rowMap.push_back(std::vector()); - rowMap[block.row].push_back(block); - } - int median = this->blocks[func.entry].col; - for(auto & blockVec : rowMap) - { - int len = int(blockVec.size()); - if(len == 1) - continue; - int bestidx = 0; - int bestdist = median; - for(int i = 0; i < len; i++) - { - auto & block = blockVec[i]; - int dist = std::abs(block.col - median); - if(dist < bestdist) - { - bestdist = dist; - bestidx = i; - } - } - for(int j = bestidx - 1; j > -1; j--) - blockVec[j].col = blockVec[j + 1].col - 2; - for(int j = bestidx + 1; j < len; j++) - blockVec[j].col = blockVec[j - 1].col + 2; - } - for(auto & blockVec : rowMap) - for(DisassemblerBlock & block : blockVec) - blocks[block.block.entry] = block;*/ - - //puts("Compute graph layout from bottom up"); - - //Prepare edge routing - EdgesVector horiz_edges, vert_edges; - horiz_edges.resize(this->blocks[func.entry].row_count + 1); - vert_edges.resize(this->blocks[func.entry].row_count + 1); - Matrix edge_valid; - edge_valid.resize(this->blocks[func.entry].row_count + 1); - for(int row = 0; row < this->blocks[func.entry].row_count + 1; row++) - { - horiz_edges[row].resize(this->blocks[func.entry].col_count + 1); - vert_edges[row].resize(this->blocks[func.entry].col_count + 1); - initVec(edge_valid[row], this->blocks[func.entry].col_count + 1, true); - for(int col = 0; col < this->blocks[func.entry].col_count + 1; col++) - { - horiz_edges[row][col].clear(); - vert_edges[row][col].clear(); - } - } - for(auto & blockIt : this->blocks) - { - DisassemblerBlock & block = blockIt.second; - edge_valid[block.row][block.col + 1] = false; - } - //puts("Prepare edge routing"); - - //Perform edge routing - for(duint blockId : blockOrder) - { - DisassemblerBlock & block = blocks[blockId]; - DisassemblerBlock & start = block; - for(duint edge : block.block.exits) - { - DisassemblerBlock & end = this->blocks[edge]; - QColor color = jmpColor; - if(edge == block.block.true_path) - color = brtrueColor; - else if(edge == block.block.false_path) - color = brfalseColor; - start.edges.push_back(this->routeEdge(horiz_edges, vert_edges, edge_valid, start, end, color)); - } - } - //puts("Perform edge routing"); - - //Compute edge counts for each row and column - std::vector col_edge_count, row_edge_count; - initVec(col_edge_count, this->blocks[func.entry].col_count + 1, 0); - initVec(row_edge_count, this->blocks[func.entry].row_count + 1, 0); - for(int row = 0; row < this->blocks[func.entry].row_count + 1; row++) - { - for(int col = 0; col < this->blocks[func.entry].col_count + 1; col++) - { - if(int(horiz_edges[row][col].size()) > row_edge_count[row]) - row_edge_count[row] = int(horiz_edges[row][col].size()); - if(int(vert_edges[row][col].size()) > col_edge_count[col]) - col_edge_count[col] = int(vert_edges[row][col].size()); - } - } - //puts("Compute edge counts for each row and column"); - - //Compute row and column sizes - std::vector col_width, row_height; - initVec(col_width, this->blocks[func.entry].col_count + 1, 0); - initVec(row_height, this->blocks[func.entry].row_count + 1, 0); - for(auto & blockIt : this->blocks) - { - DisassemblerBlock & block = blockIt.second; - if((int(block.width / 2)) > col_width[block.col]) - col_width[block.col] = int(block.width / 2); - if((int(block.width / 2)) > col_width[block.col + 1]) - col_width[block.col + 1] = int(block.width / 2); - if(int(block.height) > row_height[block.row]) - row_height[block.row] = int(block.height); - } - //puts("Compute row and column sizes"); - - //Compute row and column positions - std::vector col_x, row_y; - initVec(col_x, this->blocks[func.entry].col_count, 0); - initVec(row_y, this->blocks[func.entry].row_count, 0); - initVec(this->col_edge_x, this->blocks[func.entry].col_count + 1, 0); - initVec(this->row_edge_y, this->blocks[func.entry].row_count + 1, 0); - int x = 16; - for(int i = 0; i < this->blocks[func.entry].col_count; i++) - { - this->col_edge_x[i] = x; - x += 8 * col_edge_count[i]; - col_x[i] = x; - x += col_width[i]; - } - int y = 16; - for(int i = 0; i < this->blocks[func.entry].row_count; i++) - { - this->row_edge_y[i] = y; - y += 8 * row_edge_count[i]; - row_y[i] = y; - y += row_height[i]; - } - this->col_edge_x[this->blocks[func.entry].col_count] = x; - this->row_edge_y[this->blocks[func.entry].row_count] = y; - this->width = x + 16 + (8 * col_edge_count[this->blocks[func.entry].col_count]); - this->height = y + 16 + (8 * row_edge_count[this->blocks[func.entry].row_count]); - //puts("Compute row and column positions"); - - //Compute node positions - for(auto & blockIt : this->blocks) - { - DisassemblerBlock & block = blockIt.second; - block.x = int( - (col_x[block.col] + col_width[block.col] + 4 * col_edge_count[block.col + 1]) - (block.width / 2)); - if((block.x + block.width) > ( - col_x[block.col] + col_width[block.col] + col_width[block.col + 1] + 8 * col_edge_count[ - block.col + 1])) - { - block.x = int((col_x[block.col] + col_width[block.col] + col_width[block.col + 1] + 8 * col_edge_count[ - block.col + 1]) - block.width); - } - block.y = row_y[block.row]; - } - //puts("Compute node positions"); - - //Precompute coordinates for edges - for(auto & blockIt : this->blocks) - { - DisassemblerBlock & block = blockIt.second; - for(DisassemblerEdge & edge : block.edges) - { - auto start = edge.points[0]; - auto start_col = start.col; - auto last_index = edge.start_index; - auto last_pt = QPoint(this->col_edge_x[start_col] + (8 * last_index) + 4, - block.y + block.height + 4 - (2 * this->charWidth)); - QPolygonF pts; - pts.append(last_pt); - - for(int i = 0; i < int(edge.points.size()); i++) - { - auto end = edge.points[i]; - auto end_row = end.row; - auto end_col = end.col; - auto last_index = end.index; - QPoint new_pt; - if(start_col == end_col) - new_pt = QPoint(last_pt.x(), this->row_edge_y[end_row] + (8 * last_index) + 4); - else - new_pt = QPoint(this->col_edge_x[end_col] + (8 * last_index) + 4, last_pt.y()); - pts.push_back(new_pt); - last_pt = new_pt; - start_col = end_col; - } - - auto new_pt = QPoint(last_pt.x(), edge.dest->y + this->charWidth - 1); - pts.push_back(new_pt); - edge.polyline = pts; - - pts.clear(); - pts.append(QPoint(new_pt.x() - 3, new_pt.y() - 6)); - pts.append(QPoint(new_pt.x() + 3, new_pt.y() - 6)); - pts.append(new_pt); - edge.arrow = pts; - } - } - //puts("Precompute coordinates for edges"); - - //Adjust scroll bars for new size - auto areaSize = this->viewport()->size(); - this->adjustSize(areaSize.width(), areaSize.height()); - //puts("Adjust scroll bars for new size"); - - if(this->desired_pos) - { - //There was a position saved, navigate to it - this->horizontalScrollBar()->setValue(this->desired_pos[0]); - this->verticalScrollBar()->setValue(this->desired_pos[1]); - } - else if(this->cur_instr != 0) - { - this->show_cur_instr(this->forceCenter); - this->forceCenter = false; - } - else - { - //Ensure start node is visible - auto start_x = this->blocks[func.entry].x + this->renderXOfs + int(this->blocks[func.entry].width / 2); - this->horizontalScrollBar()->setValue(start_x - int(areaSize.width() / 2)); - this->verticalScrollBar()->setValue(0); - } - - this->analysis.update_id = this->update_id = func.update_id; - this->ready = true; - this->viewport()->update(0, 0, areaSize.width(), areaSize.height()); - //puts("Finished"); -} - -void DisassemblerGraphView::show_cur_instr(bool force) -{ - for(auto & blockIt : this->blocks) - { - DisassemblerBlock & block = blockIt.second; - auto row = int(block.block.header_text.lines.size()); - for(Instr & instr : block.block.instrs) - { - if(this->cur_instr == instr.addr) - { - //Don't update the view for blocks that are already fully in view - int xofs = this->horizontalScrollBar()->value(); - int yofs = this->verticalScrollBar()->value(); - QRect viewportRect = this->viewport()->rect(); - QPoint translation(this->renderXOfs - xofs, this->renderYOfs - yofs); - viewportRect.translate(-translation.x(), -translation.y()); - if(force || !viewportRect.contains(QRect(block.x + this->charWidth, block.y + this->charWidth, - block.width - (2 * this->charWidth), block.height - (2 * this->charWidth)))) - { - auto x = block.x + int(block.width / 2); - auto y = block.y + (2 * this->charWidth) + int((row + 0.5) * this->charHeight); - this->horizontalScrollBar()->setValue(x + this->renderXOfs - - int(this->horizontalScrollBar()->pageStep() / 2)); - this->verticalScrollBar()->setValue(y + this->renderYOfs - - int(this->verticalScrollBar()->pageStep() / 2)); - } - return; - } - row += int(instr.text.lines.size()); - } - } -} - -bool DisassemblerGraphView::navigate(duint addr) -{ - //Add address to history - //if(!mHistoryLock) - // mHistory.addVaToHistory(addr); - //Check to see if address is within current function - for(auto & blockIt : this->blocks) - { - DisassemblerBlock & block = blockIt.second; - if(block.block.entry > addr) //optimize it - continue; - auto row = int(block.block.header_text.lines.size()); - for(Instr & instr : block.block.instrs) - { - if((addr >= instr.addr) && (addr < (instr.addr + int(instr.opcode.size())))) - { - this->cur_instr = instr.addr; - this->show_cur_instr(); - this->viewport()->update(); - return true; - } - row += int(instr.text.lines.size()); - } - } - - //Check other functions for this address - duint func, instr; - if(this->analysis.find_instr(addr, func, instr)) - { - this->function = func; - this->cur_instr = instr; - this->highlight_token = nullptr; - this->ready = false; - this->desired_pos = nullptr; - this->viewport()->update(); - return true; - } - - return false; -} - -void DisassemblerGraphView::fontChanged() -{ - this->initFont(); - - if(this->ready) - { - //Rerender function to update layout - this->renderFunction(this->analysis.functions[this->function]); - } -} - -void DisassemblerGraphView::setGraphLayout(DisassemblerGraphView::LayoutType layout) -{ - this->layoutType = layout; - if(this->ready) - { - this->renderFunction(this->analysis.functions[this->function]); - } -} - -void DisassemblerGraphView::tokenizerConfigUpdatedSlot() -{ - //disasm.UpdateConfig(); - loadCurrentGraph(); -} - -void DisassemblerGraphView::loadCurrentGraph() -{ - // Read functions - QJsonDocument functionsDoc = Core()->cmdj("agj"); - QJsonArray functions = functionsDoc.array(); - - Analysis anal; - anal.ready = true; - anal.update_id = this->update_id + 1; - - QJsonValue funcRef = functions.first(); - QJsonObject func = funcRef.toObject(); - Function f; - f.ready = true; - f.entry = func["offset"].toVariant().toULongLong(); - - QString windowTitle = tr("Graph"); - QString funcName = func["name"].toString().trimmed(); - if (!funcName.isEmpty()) - { - windowTitle += " (" + funcName + ")"; - } - this->parentWidget()->setWindowTitle(windowTitle); - f.update_id = anal.update_id; - - for (QJsonValueRef blockRef : func["blocks"].toArray()) { - QJsonObject block = blockRef.toObject(); - duint fail; - duint jump; - - /* Parse Block data */ - Block b; - b.entry = block["offset"].toVariant().toULongLong(); - fail = block["fail"].toVariant().toULongLong(); - if (fail) - { - b.false_path = fail; - b.exits.push_back(fail); - } - jump = block["jump"].toVariant().toULongLong(); - if (jump) - { - if (fail) { - b.true_path = jump; - } - b.exits.push_back(jump); - } - - for (QJsonValueRef opRef : block["ops"].toArray()) { - QJsonObject op = opRef.toObject(); - Instr i; - i.addr = op["offset"].toVariant().toULongLong(); - RichTextPainter::List richText; - Colors::colorizeAssembly(richText, op["opcode"].toString(), op["type_num"].toVariant().toULongLong()); - if (op["comment"].toString().length()) { - RichTextPainter::CustomRichText_t comment; - comment.text = QString(" ; %1").arg(QByteArray::fromBase64(op["comment"].toString().toLocal8Bit()).data()); - comment.textColor = mCommentColor; - comment.flags = RichTextPainter::FlagColor; - richText.insert(richText.end(), comment); - } - i.text = Text(richText); - b.instrs.push_back(i); - } - f.blocks.push_back(b); - } - anal.functions[f.entry] = f; - anal.status = "Ready."; - anal.entry = f.entry; - this->analysis = anal; - this->function = this->analysis.entry; - this->ready = true; + return RVA_INVALID; } -/*void DisassemblerGraphView::loadGraphSlot(BridgeCFGraphList* graphList, duint addr) -{ - auto nodeCount = graphList->nodes.count; - if(nodeCount > 5000) //TODO: add configuration - { - auto title = tr("Large number of nodes"); - auto message = tr("The graph you are trying to render has a large number of nodes (%1). This can cause x64dbg to hang or crash. It is recommended to save your data before you continue.\n\nDo you want to continue rendering this graph?").arg(nodeCount); - if(QMessageBox::question(this, title, message, QMessageBox::Yes, QMessageBox::No | QMessageBox::Default) == QMessageBox::No) - { - Bridge::getBridge()->setResult(0); - return; - } - } - - currentGraph = BridgeCFGraph(graphList, true); - currentBlockMap.clear(); - this->cur_instr = addr ? addr : this->function; - this->forceCenter = true; - loadCurrentGraph(); - Bridge::getBridge()->setResult(1); -} -*/ - -void DisassemblerGraphView::refreshView() -{ - loadCurrentGraph(); - this->renderFunction(this->analysis.functions[this->function]); -} - -void DisassemblerGraphView::onSeekChanged(RVA addr) -{ - Q_UNUSED(addr); - refreshView(); -} - -void DisassemblerGraphView::graphAtSlot(duint addr) -{ - Q_UNUSED(addr); - //Bridge::getBridge()->setResult(this->navigate(addr) ? this->currentGraph.entryPoint : 0); -} - -void DisassemblerGraphView::updateGraphSlot() -{ - //this->viewport()->update(); -} - -void DisassemblerGraphView::addReferenceAction(QMenu* menu, duint addr) -{ - Q_UNUSED(menu); - Q_UNUSED(addr); - /*QAction* action = new QAction(menu); - action->setData(ToPtrString(addr)); - action->setText(getSymbolicName(addr)); - connect(action, SIGNAL(triggered()), this, SLOT(followActionSlot())); - menu->addAction(action); - */ -} - -void DisassemblerGraphView::setupContextMenu() -{ - connect(this, &DisassemblerGraphView::currentInstructionUpdated, - mMenu, &DisassemblyContextMenu::setOffset); - - QShortcut *shortcut_escape = new QShortcut(QKeySequence(Qt::Key_Escape), this); - shortcut_escape->setContext(Qt::WidgetShortcut); - connect(shortcut_escape, SIGNAL(activated()), this, SLOT(seekPrev())); -} - -void DisassemblerGraphView::keyPressEvent(QKeyEvent* event) -{ - Q_UNUSED(event); - /* - if(event->modifiers() != 0) - return; - int key = event->key(); - if(key == Qt::Key_Up) - DbgCmdExec(QString("graph dis.prev(%1), silent").arg(ToPtrString(cur_instr)).toUtf8().constData()); - else if(key == Qt::Key_Down) - DbgCmdExec(QString("graph dis.next(%1), silent").arg(ToPtrString(cur_instr)).toUtf8().constData()); - else if(key == Qt::Key_Left) - DbgCmdExec(QString("graph dis.brtrue(%1), silent").arg(ToPtrString(cur_instr)).toUtf8().constData()); - else if(key == Qt::Key_Right) - DbgCmdExec(QString("graph dis.brfalse(%1), silent").arg(ToPtrString(cur_instr)).toUtf8().constData()); - if(key == Qt::Key_Return || key == Qt::Key_Enter) - DbgCmdExec(QString("graph dis.branchdest(%1), silent").arg(ToPtrString(cur_instr)).toUtf8().constData()); - */ -} - -void DisassemblerGraphView::followDisassemblerSlot() -{ - //DbgCmdExec(QString("disasm %1").arg(ToPtrString(this->cur_instr)).toUtf8().constData()); -} +// Public Slots void DisassemblerGraphView::colorsUpdatedSlot() { disassemblyBackgroundColor = ConfigColor("gui.alt_background"); + disassemblySelectedBackgroundColor = ConfigColor("gui.background"); mDisabledBreakpointColor = disassemblyBackgroundColor; graphNodeColor = ConfigColor("gui.border"); backgroundColor = ConfigColor("gui.background"); @@ -1704,260 +379,142 @@ void DisassemblerGraphView::colorsUpdatedSlot() brfalseColor = ConfigColor("graph.false"); mCommentColor = ConfigColor("comment"); - mCommentBackgroundColor = disassemblyBackgroundColor; - /*disassemblyBackgroundColor = ConfigColor("GraphNodeBackgroundColor"); - if(!disassemblyBackgroundColor.alpha()) - disassemblyBackgroundColor = ConfigColor("DisassemblyBackgroundColor"); - graphNodeColor = ConfigColor("GraphNodeColor"); - disassemblySelectionColor = ConfigColor("DisassemblySelectionColor"); - disassemblyTracedColor = ConfigColor("DisassemblyTracedBackgroundColor"); - auto a = disassemblySelectionColor, b = disassemblyTracedColor; - disassemblyTracedSelectionColor = QColor((a.red() + b.red()) / 2, (a.green() + b.green()) / 2, (a.blue() + b.blue()) / 2); - mAutoCommentColor = ConfigColor("DisassemblyAutoCommentColor"); - mAutoCommentBackgroundColor = ConfigColor("DisassemblyAutoCommentBackgroundColor"); - mCommentColor = ConfigColor("DisassemblyCommentColor"); - mCommentBackgroundColor = ConfigColor("DisassemblyCommentBackgroundColor"); - mLabelColor = ConfigColor("DisassemblyLabelColor"); - mLabelBackgroundColor = ConfigColor("DisassemblyLabelBackgroundColor"); - mAddressColor = ConfigColor("DisassemblyAddressColor"); - mAddressBackgroundColor = ConfigColor("DisassemblyAddressBackgroundColor"); - - jmpColor = ConfigColor("GraphJmpColor"); - brtrueColor = ConfigColor("GraphBrtrueColor"); - brfalseColor = ConfigColor("GraphBrfalseColor"); - retShadowColor = ConfigColor("GraphRetShadowColor"); - indirectcallShadowColor = ConfigColor("GraphIndirectcallShadowColor"); - backgroundColor = ConfigColor("GraphBackgroundColor"); - if(!backgroundColor.alpha()) - backgroundColor = disassemblySelectionColor; - mCipColor = ConfigColor("GraphCipColor"); - mBreakpointColor = ConfigColor("GraphBreakpointColor"); - mDisabledBreakpointColor = ConfigColor("GraphDisabledBreakpointColor"); - */ - - fontChanged(); + initFont(); refreshView(); } void DisassemblerGraphView::fontsUpdatedSlot() { - fontChanged(); + this->initFont(); + refreshView(); } -void DisassemblerGraphView::shortcutsUpdatedSlot() +DisassemblerGraphView::DisassemblyBlock *DisassemblerGraphView::blockForAddress(RVA addr) { - //updateShortcuts(); -} - -void DisassemblerGraphView::toggleOverviewSlot() -{ - drawOverview = !drawOverview; - if(onlySummary) + for(auto & blockIt : disassembly_blocks) { - onlySummary = false; - mToggleSummary->setChecked(false); - loadCurrentGraph(); + DisassemblyBlock &db = blockIt.second; + for(Instr i : db.instrs) + { + if((i.addr <= addr) && (addr <= i.addr + i.size)) + { + return &db; + } + } } - else - this->viewport()->update(); + return nullptr; } -void DisassemblerGraphView::toggleSummarySlot() +void DisassemblerGraphView::onSeekChanged(RVA addr) { - drawOverview = false; - onlySummary = !onlySummary; - loadCurrentGraph(); -} - -/*void DisassemblerGraphView::selectionGetSlot(SELECTIONDATA* selection) -{ - selection->start = selection->end = cur_instr; - Bridge::getBridge()->setResult(1); -}*/ - -/*void DisassemblerGraphView::disassembleAtSlot(dsint va, dsint cip) -{ - Q_UNUSED(va); - auto cipChanged = mCip != cip; - mCip = cip; - if(syncOrigin && cipChanged) - gotoOriginSlot(); - else - this->viewport()->update(); -}*/ - -void DisassemblerGraphView::gotoExpressionSlot() -{ - /*if(!DbgIsDebugging()) - return; - if(!mGoto) - mGoto = new GotoDialog(this); - if(mGoto->exec() == QDialog::Accepted) + mMenu->setOffset(addr); + // If this seek was NOT done by us... + if(!sent_seek) { - duint value = DbgValFromString(mGoto->expressionText.toUtf8().constData()); - DbgCmdExec(QString().sprintf("graph %p, silent", value).toUtf8().constData()); + DisassemblyBlock *db = blockForAddress(addr); + if(db) + { + // This is a local address! We animated to it. + transition_dont_seek = true; + showBlock(&blocks[db->entry], true); + return; + } else { + refreshView(); + DisassemblyBlock *db = blockForAddress(addr); + if(db) + { + // This is a local address! We animated to it. + transition_dont_seek = true; + showBlock(&blocks[db->entry], false); + return; + } + } } - */ + sent_seek = false; } -void DisassemblerGraphView::gotoOriginSlot() +void DisassemblerGraphView::zoomIn() { - //DbgCmdExec("graph cip, silent"); -} - -void DisassemblerGraphView::gotoPreviousSlot() -{ - /*if(mHistory.historyHasPrev()) - { - mHistoryLock = true; - //DbgCmdExecDirect(QString("graph %1, silent").arg(ToPtrString(mHistory.historyPrev())).toUtf8().constData()); - mHistoryLock = false; - }*/ -} - -void DisassemblerGraphView::gotoNextSlot() -{ - /*if(mHistory.historyHasNext()) - { - mHistoryLock = true; - //DbgCmdExecDirect(QString("graph %1, silent").arg(ToPtrString(mHistory.historyNext())).toUtf8().constData()); - mHistoryLock = false; - }*/ -} - -void DisassemblerGraphView::toggleSyncOriginSlot() -{ - syncOrigin = !syncOrigin; - mToggleSyncOrigin->setCheckable(true); - mToggleSyncOrigin->setChecked(syncOrigin); - if(syncOrigin) - gotoOriginSlot(); -} - -void DisassemblerGraphView::refreshSlot() -{ - //DbgCmdExec(QString("graph %1, force").arg(ToPtrString(this->cur_instr)).toUtf8().constData()); -} - -void DisassemblerGraphView::saveImageSlot() -{ - saveGraph = true; + current_scale += 0.1; + auto areaSize = this->viewport()->size(); + this->adjustSize(areaSize.width(), areaSize.height()); this->viewport()->update(); } -void DisassemblerGraphView::setCommentSlot() +void DisassemblerGraphView::zoomOut() { - /* - duint wVA = this->get_cursor_pos(); - LineEditDialog mLineEdit(this); - mLineEdit.setTextMaxLength(MAX_COMMENT_SIZE - 2); - QString addr_text = ToPtrString(wVA); - char comment_text[MAX_COMMENT_SIZE] = ""; - if(!DbgIsDebugging()) - return; - if(!DbgMemIsValidReadPtr(wVA)) - return; - - if(DbgGetCommentAt((duint)wVA, comment_text)) - { - if(comment_text[0] == '\1') //automatic comment - mLineEdit.setText(QString(comment_text + 1)); - else - mLineEdit.setText(QString(comment_text)); - } - - mLineEdit.setWindowTitle(tr("Add comment at ") + addr_text); - - if(mLineEdit.exec() != QDialog::Accepted) - return; - - if(!DbgSetCommentAt(wVA, mLineEdit.editText.replace('\r', "").replace('\n', "").toUtf8().constData())) - SimpleErrorBox(this, tr("Error!"), tr("DbgSetCommentAt failed!")); - - this->refreshSlot(); - */ + current_scale -= 0.1; + current_scale = std::max(current_scale, 0.3); + auto areaSize = this->viewport()->size(); + this->adjustSize(areaSize.width(), areaSize.height()); + this->viewport()->update(); } -void DisassemblerGraphView::setLabelSlot() +void DisassemblerGraphView::takeTrue() { - /*duint wVA = this->get_cursor_pos(); - LineEditDialog mLineEdit(this); - mLineEdit.setTextMaxLength(MAX_LABEL_SIZE - 2); - QString addr_text = ToPtrString(wVA); - char label_text[MAX_LABEL_SIZE] = ""; - if(!DbgIsDebugging()) - return; - if(!DbgMemIsValidReadPtr(wVA)) - return; - - if(DbgGetLabelAt((duint)wVA, SEG_DEFAULT, label_text)) - mLineEdit.setText(QString(label_text)); - - mLineEdit.setWindowTitle(tr("Add label at ") + addr_text); -restart: - if(mLineEdit.exec() != QDialog::Accepted) - return; - - QByteArray utf8data = mLineEdit.editText.toUtf8(); - if(!utf8data.isEmpty() && DbgIsValidExpression(utf8data.constData()) && DbgValFromString(utf8data.constData()) != wVA) + DisassemblyBlock *db = blockForAddress(Core()->getOffset()); + if(db->true_path != RVA_INVALID) { - QMessageBox msg(QMessageBox::Warning, tr("The label may be in use"), - tr("The label \"%1\" may be an existing label or a valid expression. Using such label might have undesired effects. Do you still want to continue?").arg(mLineEdit.editText), - QMessageBox::Yes | QMessageBox::No, this); - msg.setWindowIcon(DIcon("compile-warning.png")); - msg.setParent(this, Qt::Dialog); - msg.setWindowFlags(msg.windowFlags() & (~Qt::WindowContextHelpButtonHint)); - if(msg.exec() == QMessageBox::No) - goto restart; + Core()->seek(db->true_path); + } + else if(blocks[db->entry].exits.size()) + { + Core()->seek(blocks[db->entry].exits[0]); } - if(!DbgSetLabelAt(wVA, utf8data.constData())) - SimpleErrorBox(this, tr("Error!"), tr("DbgSetLabelAt failed!")); - - this->refreshSlot(); - */ } -void DisassemblerGraphView::decompileSlot() +void DisassemblerGraphView::takeFalse() { - /*std::vector ranges; - ranges.reserve(currentGraph.nodes.size()); - - if(!DbgIsDebugging()) - return; - if(currentGraph.nodes.empty()) - return; - SnowmanRange r; - for(const auto & nodeIt : currentGraph.nodes) + DisassemblyBlock *db = blockForAddress(Core()->getOffset()); + if(db->false_path != RVA_INVALID) { - const BridgeCFNode & node = nodeIt.second; - r.start = node.instrs.empty() ? node.start : node.instrs[0].addr; - r.end = node.instrs.empty() ? node.end : node.instrs[node.instrs.size() - 1].addr; - BASIC_INSTRUCTION_INFO info; - DbgDisasmFastAt(r.end, &info); - r.end += info.size - 1; - ranges.push_back(r); + Core()->seek(db->false_path); } - std::sort(ranges.begin(), ranges.end(), [](const SnowmanRange & a, const SnowmanRange & b) + else if(blocks[db->entry].exits.size()) { - return a.start > b.start; - }); - emit displaySnowmanWidget(); - DecompileRanges(Bridge::getBridge()->snowmanView, ranges.data(), ranges.size()); - */ + Core()->seek(blocks[db->entry].exits[0]); + } } -void DisassemblerGraphView::followActionSlot() +void DisassemblerGraphView::seek(RVA addr, bool update_viewport) { - /*QAction* action = qobject_cast(sender()); - if(action) + sent_seek = true; + Core()->seek(addr); + if(update_viewport) { - QString data = action->data().toString(); - DbgCmdExecDirect(QString("graph %1, silent").arg(data).toUtf8().constData()); + this->viewport()->update(); } - */ } void DisassemblerGraphView::seekPrev() { Core()->seekPrev(); } + +void DisassemblerGraphView::blockClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos) +{ + RVA instr = this->getInstrForMouseEvent(block, &pos); + if(instr == RVA_INVALID) + { + return; +// + } + + seek(instr, true); + + if(event->button() == Qt::RightButton) + { + mMenu->setOffset(instr); + mMenu->exec(event->globalPos()); + } +} + +void DisassemblerGraphView::blockTransitionedTo(GraphView::GraphBlock *to) +{ + if(transition_dont_seek) + { + transition_dont_seek = false; + return; + } + seek(to->entry); +} diff --git a/src/widgets/DisassemblerGraphView.h b/src/widgets/DisassemblerGraphView.h index d60e397e..43f8a0ee 100644 --- a/src/widgets/DisassemblerGraphView.h +++ b/src/widgets/DisassemblerGraphView.h @@ -1,366 +1,206 @@ -/* x64dbg DisassemblerGraphView */ -#ifndef DISASSEMBLERGRAPHVIEW_H -#define DISASSEMBLERGRAPHVIEW_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "cutter.h" -#include "MainWindow.h" -#include "utils/RichTextPainter.h" -#include "utils/CachedFontMetrics.h" - -#define duint ut64 - -class MenuBuilder; -class CachedFontMetrics; -class GotoDialog; -class XrefBrowseDialog; -class DisassemblyContextMenu; - -class DisassemblerGraphView : public QAbstractScrollArea -{ - Q_OBJECT -public: - struct DisassemblerBlock; - - struct Point - { - int row; //point[0] - int col; //point[1] - int index; //point[2] - }; - - struct DisassemblerEdge - { - QColor color; - DisassemblerBlock* dest; - std::vector points; - int start_index = 0; - - QPolygonF polyline; - QPolygonF arrow; - - void addPoint(int row, int col, int index = 0) - { - Point point = {row, col, 0}; - this->points.push_back(point); - if(int(this->points.size()) > 1) - this->points[this->points.size() - 2].index = index; - } - }; - - struct Token - { - int start; //token[0] - int length; //token[1] - QString type; //token[2] - duint addr; //token[3] - QString name; //token[4] - }; - - struct HighlightToken - { - QString type; //highlight_token[0] - duint addr; //highlight_token[1] - QString name; //highlight_token[2] - - bool equalsToken(const Token & token) - { - return this->type == token.type && - this->addr == token.addr && - this->name == token.name; - } - - static HighlightToken* fromToken(const Token & token) - { - //TODO: memory leaks - auto result = new HighlightToken(); - result->type = token.type; - result->addr = token.addr; - result->name = token.name; - return result; - } - }; - - struct Text - { - std::vector lines; - - Text() {} - - Text(const QString & text, QColor color, QColor background) - { - RichTextPainter::List richText; - RichTextPainter::CustomRichText_t rt; - rt.highlight = false; - rt.text = text; - rt.textColor = color; - rt.textBackground = background; - rt.flags = rt.textBackground.alpha() ? RichTextPainter::FlagAll : RichTextPainter::FlagColor; - richText.push_back(rt); - lines.push_back(richText); - } - - Text(const RichTextPainter::List & richText) - { - lines.push_back(richText); - } - - QString ToQString() const - { - QString result; - for(auto & line : lines) - { - for(auto & t : line) - { - result += t.text; - } - } - return result; - } - }; - - struct Instr - { - duint addr = 0; - Text text; - std::vector opcode; //instruction bytes - }; - - struct Block - { - Text header_text; - std::vector instrs; - std::vector exits; - duint entry = 0; - duint true_path = 0; - duint false_path = 0; - bool terminal = false; - bool indirectcall = false; - }; - - struct DisassemblerBlock - { - DisassemblerBlock() {} - explicit DisassemblerBlock(Block & block) - : block(block) {} - - Block block; - std::vector edges; - std::vector incoming; - std::vector new_exits; - - qreal x = 0.0; - qreal y = 0.0; - int width = 0; - int height = 0; - int col = 0; - int col_count = 0; - int row = 0; - int row_count = 0; - }; - - struct Function - { - bool ready; - duint entry; - duint update_id; - std::vector blocks; - }; - - struct Analysis - { - duint entry = 0; - std::unordered_map functions; - bool ready = false; - duint update_id = 0; - QString status = "Analyzing..."; - - bool find_instr(duint addr, duint & func, duint & instr) - { - //TODO implement - Q_UNUSED(addr); - Q_UNUSED(func); - Q_UNUSED(instr); - return false; - } - - //dummy class - }; - - enum class LayoutType - { - Wide, - Medium, - Narrow, - }; - - DisassemblerGraphView(QWidget *parent); - ~DisassemblerGraphView(); - void initFont(); - void adjustSize(int width, int height); - void resizeEvent(QResizeEvent* event); - duint get_cursor_pos(); - void set_cursor_pos(duint addr); - std::tuple get_selection_range(); - void set_selection_range(std::tuple range); - void copy_address(); - //void analysis_thread_proc(); - //void closeRequest(); - void paintNormal(QPainter & p, QRect & viewportRect, int xofs, int yofs); - void paintOverview(QPainter & p, QRect & viewportRect, int xofs, int yofs); - void paintEvent(QPaintEvent* event); - bool isMouseEventInBlock(QMouseEvent* event); - duint getInstrForMouseEvent(QMouseEvent* event); - bool getTokenForMouseEvent(QMouseEvent* event, Token & token); - bool find_instr(duint addr, Instr & instr); - void mousePressEvent(QMouseEvent* event); - void mouseMoveEvent(QMouseEvent* event); - void mouseReleaseEvent(QMouseEvent* event); - void mouseDoubleClickEvent(QMouseEvent* event); - void prepareGraphNode(DisassemblerBlock & block); - void adjustGraphLayout(DisassemblerBlock & block, int col, int row); - void computeGraphLayout(DisassemblerBlock & block); - void setupContextMenu(); - void keyPressEvent(QKeyEvent* event); - - template - using Matrix = std::vector>; - using EdgesVector = Matrix>; - bool isEdgeMarked(EdgesVector & edges, int row, int col, int index); - void markEdge(EdgesVector & edges, int row, int col, int index, bool used = true); - int findHorizEdgeIndex(EdgesVector & edges, int row, int min_col, int max_col); - int findVertEdgeIndex(EdgesVector & edges, int col, int min_row, int max_row); - DisassemblerEdge routeEdge(EdgesVector & horiz_edges, EdgesVector & vert_edges, Matrix & edge_valid, DisassemblerBlock & start, DisassemblerBlock & end, QColor color); - void renderFunction(Function & func); - void show_cur_instr(bool force = false); - bool navigate(duint addr); - void fontChanged(); - void setGraphLayout(LayoutType layout); - - //VaHistory mHistory; - -signals: - void displaySnowmanWidget(); - void currentInstructionUpdated(duint); - -public slots: - void refreshView(); - void onSeekChanged(RVA); - //void loadGraphSlot(BridgeCFGraphList* graph, duint addr); - void graphAtSlot(duint addr); - void updateGraphSlot(); - void followDisassemblerSlot(); - void colorsUpdatedSlot(); - void fontsUpdatedSlot(); - void shortcutsUpdatedSlot(); - void toggleOverviewSlot(); - void toggleSummarySlot(); - //void selectionGetSlot(SELECTIONDATA* selection); - void tokenizerConfigUpdatedSlot(); - void loadCurrentGraph(); - //void disassembleAtSlot(dsint va, dsint cip); - void gotoExpressionSlot(); - void gotoOriginSlot(); - void gotoPreviousSlot(); - void gotoNextSlot(); - void toggleSyncOriginSlot(); - void followActionSlot(); - void refreshSlot(); - void saveImageSlot(); - void setCommentSlot(); - void setLabelSlot(); - void decompileSlot(); - - void seekPrev(); - -private: - QString status; - Analysis analysis; - duint function; - int baseline; - qreal charWidth; - int charHeight; - int charOffset; - int width; - int height; - int renderWidth; - int renderHeight; - int renderXOfs; - int renderYOfs; - duint cur_instr; - int scroll_base_x; - int scroll_base_y; - duint update_id; - bool scroll_mode; - bool ready; - int* desired_pos; - std::unordered_map blocks; - HighlightToken* highlight_token; - std::vector col_edge_x; - std::vector row_edge_y; - CachedFontMetrics* mFontMetrics; - MenuBuilder* mMenuBuilder; - bool drawOverview; - bool onlySummary; - bool syncOrigin; - int overviewXOfs; - int overviewYOfs; - qreal overviewScale; - duint mCip; - bool forceCenter; - bool saveGraph; - bool mHistoryLock; //Don't add a history while going to previous/next - LayoutType layoutType; - - QAction* mToggleOverview; - QAction* mToggleSummary; - QAction* mToggleSyncOrigin; - - QColor disassemblyBackgroundColor; - QColor disassemblySelectionColor; - QColor disassemblyTracedColor; - QColor disassemblyTracedSelectionColor; - QColor jmpColor; - QColor brtrueColor; - QColor brfalseColor; - QColor retShadowColor; - QColor indirectcallShadowColor; - QColor backgroundColor; - QColor mAutoCommentColor; - QColor mAutoCommentBackgroundColor; - QColor mCommentColor; - QColor mCommentBackgroundColor; - QColor mLabelColor; - QColor mLabelBackgroundColor; - QColor graphNodeColor; - QColor mAddressColor; - QColor mAddressBackgroundColor; - QColor mCipColor; - QColor mBreakpointColor; - QColor mDisabledBreakpointColor; - - //BridgeCFGraph currentGraph; - std::unordered_map currentBlockMap; - //QBeaEngine disasm; - GotoDialog* mGoto; - DisassemblyContextMenu* mMenu; - - void addReferenceAction(QMenu* menu, duint addr); -}; - -#endif // DISASSEMBLERGRAPHVIEW_H +#ifndef DISASSEMBLERGRAPHVIEW_H +#define DISASSEMBLERGRAPHVIEW_H + +// Based on the DisassemblerGraphView from x64dbg + +#include +#include + +#include "widgets/GraphView.h" +#include "menus/DisassemblyContextMenu.h" +#include "utils/RichTextPainter.h" + +class DisassemblerGraphView : public GraphView +{ + Q_OBJECT + + + struct Token + { + int start; //token[0] + int length; //token[1] + QString type; //token[2] + ut64 addr; //token[3] + QString name; //token[4] + }; + + struct HighlightToken + { + QString type; //highlight_token[0] + ut64 addr; //highlight_token[1] + QString name; //highlight_token[2] + + bool equalsToken(const Token & token) + { + return this->type == token.type && + this->addr == token.addr && + this->name == token.name; + } + + static HighlightToken* fromToken(const Token & token) + { + //TODO: memory leaks + auto result = new HighlightToken(); + result->type = token.type; + result->addr = token.addr; + result->name = token.name; + return result; + } + }; + + struct Text + { + std::vector lines; + + Text() {} + + Text(const QString & text, QColor color, QColor background) + { + RichTextPainter::List richText; + RichTextPainter::CustomRichText_t rt; + rt.highlight = false; + rt.text = text; + rt.textColor = color; + rt.textBackground = background; + rt.flags = rt.textBackground.alpha() ? RichTextPainter::FlagAll : RichTextPainter::FlagColor; + richText.push_back(rt); + lines.push_back(richText); + } + + Text(const RichTextPainter::List & richText) + { + lines.push_back(richText); + } + + QString ToQString() const + { + QString result; + for(auto & line : lines) + { + for(auto & t : line) + { + result += t.text; + } + } + return result; + } + }; + + struct Instr + { + ut64 addr = 0; + ut64 size = 0; + Text text; + std::vector opcode; //instruction bytes + }; + + + struct DisassemblyBlock + { + Text header_text; + std::vector instrs; + ut64 entry = 0; + ut64 true_path = 0; + ut64 false_path = 0; + bool terminal = false; + bool indirectcall = false; + }; + + struct Function + { + bool ready; + ut64 entry; + ut64 update_id; + std::vector blocks; + }; + + struct Analysis + { + ut64 entry = 0; + std::unordered_map functions; + bool ready = false; + ut64 update_id = 0; + QString status = "Analyzing..."; + + bool find_instr(ut64 addr, ut64 & func, ut64 & instr) + { + //TODO implement + Q_UNUSED(addr); + Q_UNUSED(func); + Q_UNUSED(instr); + return false; + } + + //dummy class + }; + +public: + DisassemblerGraphView(QWidget *parent); + std::unordered_map disassembly_blocks; + virtual void drawBlock(QPainter & p, GraphView::GraphBlock &block) override; + virtual void blockClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos) override; + virtual GraphView::EdgeConfiguration edgeConfiguration(GraphView::GraphBlock &from, GraphView::GraphBlock *to) override; + virtual void blockTransitionedTo(GraphView::GraphBlock *to) override; + + void loadCurrentGraph(); +// bool navigate(ut64 addr); + +public slots: + void refreshView(); + void colorsUpdatedSlot(); + void fontsUpdatedSlot(); + void onSeekChanged(RVA addr); + + void zoomIn(); + void zoomOut(); + + void takeTrue(); + void takeFalse(); +private slots: + void seekPrev(); + +private: + bool transition_dont_seek = false; + bool sent_seek = false; + + HighlightToken* highlight_token; + // Font data + CachedFontMetrics* mFontMetrics; + qreal charWidth; + qreal charHeight; + int charOffset; + int baseline; + + DisassemblyContextMenu* mMenu; + + void initFont(); + void prepareGraphNode(GraphBlock &block); + RVA getInstrForMouseEvent(GraphBlock &block, QPoint* point); + DisassemblyBlock *blockForAddress(RVA addr); + void seek(RVA addr, bool update_viewport=true); + + QColor disassemblyBackgroundColor; + QColor disassemblySelectedBackgroundColor; + QColor disassemblySelectionColor; + QColor disassemblyTracedColor; + QColor disassemblyTracedSelectionColor; + QColor jmpColor; + QColor brtrueColor; + QColor brfalseColor; + QColor retShadowColor; + QColor indirectcallShadowColor; + QColor mAutoCommentColor; + QColor mAutoCommentBackgroundColor; + QColor mCommentColor; + QColor mCommentBackgroundColor; + QColor mLabelColor; + QColor mLabelBackgroundColor; + QColor graphNodeColor; + QColor mAddressColor; + QColor mAddressBackgroundColor; + QColor mCipColor; + QColor mBreakpointColor; + QColor mDisabledBreakpointColor; +}; + +#endif // DISASSEMBLERGRAPHVIEW_H diff --git a/src/widgets/GraphView.cpp b/src/widgets/GraphView.cpp new file mode 100644 index 00000000..32837184 --- /dev/null +++ b/src/widgets/GraphView.cpp @@ -0,0 +1,892 @@ +#include "GraphView.h" + +#include +#include +#include +#include + + +GraphView::GraphView(QWidget *parent) + : QAbstractScrollArea(parent) +{ + this->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + this->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + this->horizontalScrollBar()->setSingleStep(this->charWidth); + this->verticalScrollBar()->setSingleStep(this->charWidth); + QSize areaSize = this->viewport()->size(); + this->adjustSize(areaSize.width(), areaSize.height()); +} + +GraphView::~GraphView() +{ + // TODO: Cleanups +} + +// Vector functions +template +static void removeFromVec(std::vector & vec, T elem) +{ + vec.erase(std::remove(vec.begin(), vec.end(), elem), vec.end()); +} + +template +static void initVec(std::vector & vec, size_t size, T value) +{ + vec.resize(size); + for(size_t i = 0; i < size; i++) + vec[i] = value; +} + +// Callbacks +void GraphView::drawBlock(QPainter & p, GraphView::GraphBlock &block) +{ + Q_UNUSED(p); + Q_UNUSED(block); + qWarning() << "Draw block not overriden!"; +} + +void GraphView::blockClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos) +{ + Q_UNUSED(block); + Q_UNUSED(event); + Q_UNUSED(pos); + qWarning() << "Block clicked not overridden!"; +} + +void GraphView::blockTransitionedTo(GraphView::GraphBlock *to) +{ + Q_UNUSED(to); + qWarning() << "blockTransitionedTo not overridden!"; +} + +GraphView::EdgeConfiguration GraphView::edgeConfiguration(GraphView::GraphBlock &from, GraphView::GraphBlock *to) +{ + Q_UNUSED(from); + Q_UNUSED(to); + qWarning() << "Edge configuration not overridden!"; + EdgeConfiguration ec; + return ec; +} + +void GraphView::adjustSize(int new_width, int new_height) +{ + double hfactor = 0.0; + double vfactor = 0.0; + if(horizontalScrollBar()->maximum()) + { + hfactor = (double)horizontalScrollBar()->value() / (double)horizontalScrollBar()->maximum(); + } + if(verticalScrollBar()->maximum()) + { + vfactor = (double)verticalScrollBar()->value() / (double)verticalScrollBar()->maximum(); + } + + //Update scroll bar information + horizontalScrollBar()->setPageStep(new_width); + horizontalScrollBar()->setRange(0, this->width - (new_width/current_scale)); + verticalScrollBar()->setPageStep(new_height); + verticalScrollBar()->setRange(0, this->height - (new_height/current_scale)); + horizontalScrollBar()->setValue((int)((double)horizontalScrollBar()->maximum() * hfactor)); + verticalScrollBar()->setValue((int)((double)verticalScrollBar()->maximum() * vfactor)); +} + +// This calculates the full graph starting at block entry. +void GraphView::computeGraph(ut64 entry) +{ + QSize areaSize = this->viewport()->size(); + + // Populate incoming lists + for(auto &blockIt : this->blocks) + { + GraphBlock &block = blockIt.second; + for(auto & edge : block.exits) + { + this->blocks[edge].incoming.push_back(block.entry); + } + } + + std::unordered_set visited; + visited.insert(entry); + std::queue queue; + std::vector block_order; + queue.push(entry); + + bool changed = true; + while(changed) + { + changed = false; + + // Pick nodes with single entrypoints + while(!queue.empty()) + { + GraphBlock &block = this->blocks[queue.front()]; + queue.pop(); + block_order.push_back(block.entry); + for(ut64 edge : block.exits) + { + // Skip edge if we already visited it + if(visited.count(edge)) + { + continue; + } + + // Some edges might not be available + if(!this->blocks.count(edge)) + { + continue; + } + + // If this node has no other incoming edges, add it to the graph layout + if(this->blocks[edge].incoming.size() == 1) + { + removeFromVec(this->blocks[edge].incoming, block.entry); + block.new_exits.push_back(edge); + queue.push(this->blocks[edge].entry); + visited.insert(edge); + changed = true; + } else { + // Remove from incoming edges + removeFromVec(this->blocks[edge].incoming, block.entry); + } + } + } + + // No more nodes satisfy constraints, pick a node to continue constructing the graph + ut64 best = 0; + int best_edges; + ut64 best_parent; + for(auto & blockIt : this->blocks) + { + GraphBlock &block = blockIt.second; + // Skip blocks we haven't visited yet + if(!visited.count(block.entry)) + { + continue; + } + for(ut64 edge : block.exits) + { + // If we already visited the exit, skip it + if(visited.count(edge)) + { + continue; + } + if(!this->blocks.count(edge)) + { + continue; + } + // find best edge + if((best == 0) || ((int)this->blocks[edge].incoming.size() < best_edges) || ( + ((int)this->blocks[edge].incoming.size() == best_edges) && (edge < best))) + { + best = edge; + best_edges = this->blocks[edge].incoming.size(); + best_parent = block.entry; + } + } + } + if(best != 0) + { + GraphBlock &best_parentb = this->blocks[best_parent]; + removeFromVec(this->blocks[best].incoming, best_parentb.entry); + best_parentb.new_exits.push_back(best); + visited.insert(best); + queue.push(best); + changed = true; + } + } + + this->computeGraphLayout(this->blocks[entry]); + + // Prepare edge routing + GraphBlock &entryb = this->blocks[entry]; + EdgesVector horiz_edges, vert_edges; + horiz_edges.resize(entryb.row_count + 1); + vert_edges.resize(entryb.row_count + 1); + Matrix edge_valid; + edge_valid.resize(entryb.row_count + 1); + for(int row = 0; row < entryb.row_count + 1; row++) + { + horiz_edges[row].resize(entryb.col_count +1); + vert_edges[row].resize(entryb.col_count + 1); + initVec(edge_valid[row], entryb.col_count + 1, true); + for(int col = 0; col < entryb.col_count + 1; col++) + { + horiz_edges[row][col].clear(); + vert_edges[row][col].clear(); + } + } + + for(auto & blockIt : this->blocks) + { + GraphBlock &block = blockIt.second; + edge_valid[block.row][block.col + 1] = false; + } + + // Perform edge routing + for(ut64 block_id : block_order) + { + GraphBlock &block = blocks[block_id]; + GraphBlock &start = block; + for(ut64 edge : block.exits) + { + GraphBlock &end = this->blocks[edge]; + start.edges.push_back(this->routeEdge(horiz_edges, vert_edges, edge_valid, start, end, QColor(255, 0, 0))); + } + } + + // Compute edge counts for each row and column + std::vector col_edge_count, row_edge_count; + initVec(col_edge_count, entryb.col_count + 1, 0); + initVec(row_edge_count, entryb.row_count + 1, 0); + for(int row = 0; row < entryb.row_count + 1; row++) + { + for(int col = 0; col < entryb.col_count + 1; col++) + { + if(int(horiz_edges[row][col].size()) > row_edge_count[row]) + row_edge_count[row] = int(horiz_edges[row][col].size()); + if(int(vert_edges[row][col].size()) > col_edge_count[col]) + col_edge_count[col] = int(vert_edges[row][col].size()); + } + } + + + //Compute row and column sizes + std::vector col_width, row_height; + initVec(col_width, entryb.col_count + 1, 0); + initVec(row_height, entryb.row_count + 1, 0); + for(auto & blockIt : this->blocks) + { + GraphBlock &block = blockIt.second; + if((int(block.width / 2)) > col_width[block.col]) + col_width[block.col] = int(block.width / 2); + if((int(block.width / 2)) > col_width[block.col + 1]) + col_width[block.col + 1] = int(block.width / 2); + if(int(block.height) > row_height[block.row]) + row_height[block.row] = int(block.height); + } + + // Compute row and column positions + std::vector col_x, row_y; + initVec(col_x, entryb.col_count, 0); + initVec(row_y, entryb.row_count, 0); + initVec(this->col_edge_x, entryb.col_count + 1, 0); + initVec(this->row_edge_y, entryb.row_count + 1, 0); + int x = 16; + for(int i = 0; i < entryb.col_count; i++) + { + this->col_edge_x[i] = x; + x += 8 * col_edge_count[i]; + col_x[i] = x; + x += col_width[i]; + } + int y = 16; + for(int i = 0; i < entryb.row_count; i++) + { + this->row_edge_y[i] = y; + y += block_vertical_margin * row_edge_count[i]; + row_y[i] = y; + y += row_height[i]; + } + this->col_edge_x[entryb.col_count] = x; + this->row_edge_y[entryb.row_count] = y; + this->width = x + 16 + (8 * col_edge_count[entryb.col_count]); + this->height = y + 16 + (8 * row_edge_count[entryb.row_count]); + + //Compute node positions + for(auto & blockIt : this->blocks) + { + GraphBlock &block = blockIt.second; + block.x = int( + (col_x[block.col] + col_width[block.col] + 4 * col_edge_count[block.col + 1]) - (block.width / 2)); + if((block.x + block.width) > ( + col_x[block.col] + col_width[block.col] + col_width[block.col + 1] + 8 * col_edge_count[ + block.col + 1])) + { + block.x = int((col_x[block.col] + col_width[block.col] + col_width[block.col + 1] + 8 * col_edge_count[ + block.col + 1]) - block.width); + } + block.y = row_y[block.row]; + } + + // Precompute coordinates for edges + for(auto & blockIt : this->blocks) + { + GraphBlock &block = blockIt.second; + + for(GraphEdge & edge : block.edges) + { + auto start = edge.points[0]; + auto start_col = start.col; + auto last_index = edge.start_index; + auto first_pt = QPoint(this->col_edge_x[start_col] + (8 * last_index) + 4, + block.y + block.height); + auto last_pt = first_pt; + QPolygonF pts; + pts.append(last_pt); + + for(int i = 0; i < int(edge.points.size()); i++) + { + auto end = edge.points[i]; + auto end_row = end.row; + auto end_col = end.col; + auto last_index = end.index; + QPoint new_pt; + // block_vertical_margin/2 gives the margin from block to the horizontal lines + if(start_col == end_col) + new_pt = QPoint(last_pt.x(), this->row_edge_y[end_row] + (8 * last_index) + (block_vertical_margin/2)); + else + new_pt = QPoint(this->col_edge_x[end_col] + (8 * last_index) + 4, last_pt.y()); + pts.push_back(new_pt); + last_pt = new_pt; + start_col = end_col; + } + + EdgeConfiguration ec = edgeConfiguration(block, edge.dest); + + auto new_pt = QPoint(last_pt.x(), edge.dest->y - 1); + pts.push_back(new_pt); + edge.polyline = pts; + edge.color = ec.color; + if(ec.start_arrow) + { + pts.clear(); + pts.append(QPoint(first_pt.x() - 3, first_pt.y() + 6)); + pts.append(QPoint(first_pt.x() + 3, first_pt.y() + 6)); + pts.append(first_pt); + edge.arrow_start = pts; + } + pts.clear(); + pts.append(QPoint(new_pt.x() - 3, new_pt.y() - 6)); + pts.append(QPoint(new_pt.x() + 3, new_pt.y() - 6)); + pts.append(new_pt); + edge.arrow_end = pts; + } + } + + this->ready = true; + + this->viewport()->update(); + areaSize = this->viewport()->size(); + this->adjustSize(areaSize.width(), areaSize.height()); +} + +void GraphView::paintEvent(QPaintEvent* event) +{ + Q_UNUSED(event); + QPainter p(this->viewport()); + int render_offset_x = -this->horizontalScrollBar()->value() * current_scale; + int render_offset_y = -this->verticalScrollBar()->value() * current_scale; + int render_width = this->viewport()->size().width() / current_scale; + int render_height = this->viewport()->size().height() / current_scale; + + // Draw background + QRect viewportRect(this->viewport()->rect().topLeft(), this->viewport()->rect().bottomRight() - QPoint(1, 1)); + p.setBrush(backgroundColor); + p.drawRect(viewportRect); + p.setBrush(Qt::black); + + p.translate(render_offset_x, render_offset_y); + p.scale(current_scale, current_scale); + + + // Draw blocks + for(auto & blockIt : this->blocks) + { + GraphBlock &block = blockIt.second; + + // Check if block is visible + if((block.x + block.width > -render_offset_x) || + (block.y + block.height > -render_offset_y) || + (-render_offset_x + render_width > block.x) || + (-render_offset_y + render_height > block.y)) + { + // Only draw block if it is visible + drawBlock(p, block); + } + + p.setBrush(Qt::gray); + + // Always draw edges + // TODO: Only draw edges if they are actually visible ... + // Draw edges + for(GraphEdge & edge : block.edges) + { + EdgeConfiguration ec = edgeConfiguration(block, edge.dest); + QPen pen(edge.color); +// if(blockSelected) +// pen.setStyle(Qt::DashLine); + p.setPen(pen); + p.setBrush(edge.color); + p.drawPolyline(edge.polyline); + pen.setStyle(Qt::SolidLine); + p.setPen(pen); + if(ec.start_arrow) + { + p.drawConvexPolygon(edge.arrow_start); + } + if(ec.end_arrow) + { + p.drawConvexPolygon(edge.arrow_end); + } + } + + } +} + +// Prepare graph +// This computes the position and (row/col based) size of the block +// Recursively calls itself for each child of the GraphBlock +void GraphView::computeGraphLayout(GraphBlock &block) +{ + int col = 0; + int row_count = 1; + int childColumn = 0; + bool singleChild = block.new_exits.size() == 1; + // Compute all children nodes + for(size_t i = 0; i < block.new_exits.size(); i++) + { + ut64 edge = block.new_exits[i]; + GraphBlock &edgeb = this->blocks[edge]; + this->computeGraphLayout(edgeb); + row_count = std::max(edgeb.row_count + 1, row_count); + childColumn = edgeb.col; + } + + if(this->layoutType != LayoutType::Wide && block.new_exits.size() == 2) + { + GraphBlock &left = this->blocks[block.new_exits[0]]; + GraphBlock &right= this->blocks[block.new_exits[1]]; + if(left.new_exits.size() == 0) + { + left.col = right.col - 2; + int add = left.col < 0 ? - left.col : 0; + this->adjustGraphLayout(right, add, 1); + this->adjustGraphLayout(left, add, 1); + col = right.col_count + add; + } + else if(right.new_exits.size() == 0) + { + this->adjustGraphLayout(left, 0, 1); + this->adjustGraphLayout(right, left.col + 2, 1); + col = std::max(left.col_count, right.col + 2); + } + else + { + this->adjustGraphLayout(left, 0, 1); + this->adjustGraphLayout(right, left.col_count, 1); + col = left.col_count + right.col_count; + } + block.col_count = std::max(2, col); + if(layoutType == LayoutType::Medium) + { + block.col = (left.col + right.col) / 2; + } + else + { + block.col = singleChild ? childColumn : (col - 2) / 2; + } + } + else + { + for(ut64 edge : block.new_exits) + { + this->adjustGraphLayout(this->blocks[edge], col, 1); + col += this->blocks[edge].col_count; + } + if(col >= 2) + { + // Place this node centered over the child nodes + block.col = singleChild ? childColumn : (col - 2) / 2; + block.col_count = col; + } + else + { + //No child nodes, set single node's width (nodes are 2 columns wide to allow + //centering over a branch) + block.col = 0; + block.col_count = 2; + } + } + block.row = 0; + block.row_count = row_count; +} + +// Edge computing stuff +bool GraphView::isEdgeMarked(EdgesVector & edges, int row, int col, int index) +{ + if(index >= int(edges[row][col].size())) + return false; + return edges[row][col][index]; +} + +void GraphView::markEdge(EdgesVector & edges, int row, int col, int index, bool used) +{ + while(int(edges[row][col].size()) <= index) + edges[row][col].push_back(false); + edges[row][col][index] = used; +} + +GraphView::GraphEdge GraphView::routeEdge(EdgesVector & horiz_edges, EdgesVector & vert_edges, Matrix & edge_valid, GraphBlock &start, GraphBlock &end, QColor color) +{ + GraphEdge edge; + edge.color = color; + edge.dest = &end; + + //Find edge index for initial outgoing line + int i = 0; + while(true) + { + if(!this->isEdgeMarked(vert_edges, start.row + 1, start.col + 1, i)) + break; + i += 1; + } + this->markEdge(vert_edges, start.row + 1, start.col + 1, i); + edge.addPoint(start.row + 1, start.col + 1); + edge.start_index = i; + bool horiz = false; + + //Find valid column for moving vertically to the target node + int min_row, max_row; + if(end.row < (start.row + 1)) + { + min_row = end.row; + max_row = start.row + 1; + } + else + { + min_row = start.row + 1; + max_row = end.row; + } + int col = start.col + 1; + if(min_row != max_row) + { + auto checkColumn = [min_row, max_row, &edge_valid](int column) + { + if(column < 0 || column >= int(edge_valid[min_row].size())) + return false; + for(int row = min_row; row < max_row; row++) + { + if(!edge_valid[row][column]) + { + return false; + } + } + return true; + }; + + if(!checkColumn(col)) + { + if(checkColumn(end.col + 1)) + { + col = end.col + 1; + } + else + { + int ofs = 0; + while(true) + { + col = start.col + 1 - ofs; + if(checkColumn(col)) + { + break; + } + + col = start.col + 1 + ofs; + if(checkColumn(col)) + { + break; + } + + ofs += 1; + } + } + } + } + + if(col != (start.col + 1)) + { + //Not in same column, need to generate a line for moving to the correct column + int min_col, max_col; + if(col < (start.col + 1)) + { + min_col = col; + max_col = start.col + 1; + } + else + { + min_col = start.col + 1; + max_col = col; + } + int index = this->findHorizEdgeIndex(horiz_edges, start.row + 1, min_col, max_col); + edge.addPoint(start.row + 1, col, index); + horiz = true; + } + + if(end.row != (start.row + 1)) + { + //Not in same row, need to generate a line for moving to the correct row + if(col == (start.col + 1)) + this->markEdge(vert_edges, start.row + 1, start.col + 1, i, false); + int index = this->findVertEdgeIndex(vert_edges, col, min_row, max_row); + if(col == (start.col + 1)) + edge.start_index = index; + edge.addPoint(end.row, col, index); + horiz = false; + } + + if(col != (end.col + 1)) + { + //Not in ending column, need to generate a line for moving to the correct column + int min_col, max_col; + if(col < (end.col + 1)) + { + min_col = col; + max_col = end.col + 1; + } + else + { + min_col = end.col + 1; + max_col = col; + } + int index = this->findHorizEdgeIndex(horiz_edges, end.row, min_col, max_col); + edge.addPoint(end.row, end.col + 1, index); + horiz = true; + } + + //If last line was horizontal, choose the ending edge index for the incoming edge + if(horiz) + { + int index = this->findVertEdgeIndex(vert_edges, end.col + 1, end.row, end.row); + edge.points[int(edge.points.size()) - 1].index = index; + } + + return edge; +} + +int GraphView::findHorizEdgeIndex(EdgesVector & edges, int row, int min_col, int max_col) +{ + //Find a valid index + int i = 0; + while(true) + { + bool valid = true; + for(int col = min_col; col < max_col + 1; col++) + if(isEdgeMarked(edges, row, col, i)) + { + valid = false; + break; + } + if(valid) + break; + i++; + } + + //Mark chosen index as used + for(int col = min_col; col < max_col + 1; col++) + this->markEdge(edges, row, col, i); + return i; +} + +int GraphView::findVertEdgeIndex(EdgesVector & edges, int col, int min_row, int max_row) +{ + //Find a valid index + int i = 0; + while(true) + { + bool valid = true; + for(int row = min_row; row < max_row + 1; row++) + if(isEdgeMarked(edges, row, col, i)) + { + valid = false; + break; + } + if(valid) + break; + i++; + } + + //Mark chosen index as used + for(int row = min_row; row < max_row + 1; row++) + this->markEdge(edges, row, col, i); + return i; +} + + +void GraphView::showBlock(GraphBlock &block, bool animated) +{ + showBlock(&block, animated); +} + +void GraphView::showBlock(GraphBlock *block, bool animated) +{ + int render_width = viewport()->size().width() / current_scale; + + + // Show block middle of X + int target_x = (block->x + (block->width / 2)) - (render_width/2); + int show_block_offset_y = 30; + // But beginning of Y (so we show the top of the block) + int target_y = block->y - show_block_offset_y; + + target_x = std::max(0, target_x); + target_y = std::max(0, target_y); + target_x = std::min(this->horizontalScrollBar()->maximum(), target_x); + target_y = std::min(this->verticalScrollBar()->maximum(), target_y); + if(animated) + { + QPropertyAnimation *animation_x = new QPropertyAnimation(this->horizontalScrollBar(), "value"); + animation_x->setDuration(500); + animation_x->setStartValue(this->horizontalScrollBar()->value()); + animation_x->setEndValue(target_x); + animation_x->setEasingCurve(QEasingCurve::InOutQuad); + animation_x->start(); + QPropertyAnimation *animation_y = new QPropertyAnimation(this->verticalScrollBar(), "value"); + animation_y->setDuration(500); + animation_y->setStartValue(this->verticalScrollBar()->value()); + animation_y->setEndValue(target_y); + animation_y->setEasingCurve(QEasingCurve::InOutQuad); + animation_y->start(); + } else { + this->horizontalScrollBar()->setValue(target_x); + this->verticalScrollBar()->setValue(target_y); + } + + blockTransitionedTo(block); + + this->viewport()->update(); +} + +void GraphView::adjustGraphLayout(GraphBlock &block, int col, int row) +{ + block.col += col; + block.row += row; + for(ut64 edge : block.new_exits) + { + this->adjustGraphLayout(this->blocks[edge], col, row); + } +} + +void GraphView::addBlock(GraphView::GraphBlock block) +{ + this->blocks[block.entry] = block; +} + +void GraphView::setEntry(ut64 e) +{ + this->entry = e; +} + +bool GraphView::checkPointClicked(QPointF &point, int x, int y, bool above_y) +{ + int half_target_size = 5; + if((point.x() - half_target_size < x) && + (point.y() - (above_y ? (2 * half_target_size) : 0) < y) && + (x < point.x() + half_target_size) && + (y < point.y() + (above_y ? 0 : (2 * half_target_size)))) + { + return true; + } + return false; +} + + +void GraphView::resizeEvent(QResizeEvent* event) +{ + adjustSize(event->size().width(), event->size().height()); +} + +// Mouse events +void GraphView::mousePressEvent(QMouseEvent *event) +{ + int x = (event->pos().x() / current_scale) + horizontalScrollBar()->value(); + int y = (event->pos().y() / current_scale) + verticalScrollBar()->value(); + + // Check if a block was clicked + for(auto & blockIt : blocks) + { + GraphBlock &block = blockIt.second; + + if((block.x <= x) && (block.y <= y) && + (x <= block.x + block.width) & (y <= block.y + block.height)) + { + QPoint pos = QPoint(x - block.x, y - block.y); + blockClicked(block, event, pos); + // Don't do anything else here! blockClicked might seek and + // all our data is invalid then. + return; + } + } + + // Check if a line beginning/end was clicked + for(auto & blockIt : this->blocks) + { + GraphBlock &block = blockIt.second; + for(GraphEdge & edge : block.edges) + { + if(edge.polyline.length() < 2) + { + continue; + } + QPointF start = edge.polyline.first(); + QPointF end = edge.polyline.last(); + if(checkPointClicked(start, x, y)) + { + showBlock(edge.dest, true); + // TODO: Callback to child + return; + break; + } + if(checkPointClicked(end, x, y, true)) + { + showBlock(block, true); + // TODO: Callback to child + return; + break; + } + } + } + + // No block was clicked + if(event->button() == Qt::LeftButton) + { + //Left click outside any block, enter scrolling mode + this->scroll_base_x = event->x(); + this->scroll_base_y = event->y(); + this->scroll_mode = true; + this->setCursor(Qt::ClosedHandCursor); + this->viewport()->grabMouse(); + } + +} + +void GraphView::mouseMoveEvent(QMouseEvent* event) +{ + if(this->scroll_mode) + { + int x_delta = this->scroll_base_x - event->x(); + int y_delta = this->scroll_base_y - event->y(); + this->scroll_base_x = event->x(); + this->scroll_base_y = event->y(); + this->horizontalScrollBar()->setValue(this->horizontalScrollBar()->value() + x_delta); + this->verticalScrollBar()->setValue(this->verticalScrollBar()->value() + y_delta); + } +} + +void GraphView::mouseReleaseEvent(QMouseEvent* event) +{ + // TODO +// if(event->button() == Qt::ForwardButton) +// gotoNextSlot(); +// else if(event->button() == Qt::BackButton) +// gotoPreviousSlot(); + + if(event->button() != Qt::LeftButton) + return; + + if(this->scroll_mode) + { + this->scroll_mode = false; + this->setCursor(Qt::ArrowCursor); + this->viewport()->releaseMouse(); + } +} diff --git a/src/widgets/GraphView.h b/src/widgets/GraphView.h new file mode 100644 index 00000000..51a71c0b --- /dev/null +++ b/src/widgets/GraphView.h @@ -0,0 +1,164 @@ +#ifndef GRAPHVIEW_H +#define GRAPHVIEW_H + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "cutter.h" + +class GraphView : public QAbstractScrollArea +{ + Q_OBJECT + + enum class LayoutType + { + Wide, + Medium, + Narrow, + }; +public: + struct GraphBlock; + + struct Point + { + int row; //point[0] + int col; //point[1] + int index; //point[2] + }; + + struct GraphEdge + { + QColor color; + GraphBlock *dest; + std::vector points; + int start_index = 0; + + QPolygonF polyline; + QPolygonF arrow_start; + QPolygonF arrow_end; + + void addPoint(int row, int col, int index = 0) + { + Point point = {row, col, 0}; + this->points.push_back(point); + if(int(this->points.size()) > 1) + this->points[this->points.size() - 2].index = index; + } + }; + + struct GraphBlock { + qreal x = 0.0; + qreal y = 0.0; + int width = 0; + int height = 0; + // This is a unique identifier, e.g. offset in the case of r2 blocks + ut64 entry; + // This contains unique identifiers to entries + // Outgoing edges + std::vector exits; + // Incoming edges + std::vector incoming; + // TODO what is this + std::vector new_exits; + + // Number of rows in block + int row_count; + // Number of columns in block + int col_count; + // Column in which the block is + int col; + // Row in which the block is + int row; + + // Edges + std::vector edges; + }; + + struct EdgeConfiguration + { + QColor color = QColor(128, 128, 128); + bool start_arrow = false; + bool end_arrow = true; + }; + + GraphView(QWidget *parent); + ~GraphView(); + void paintEvent(QPaintEvent* event) override; + + // Show a block centered. Animates to it if animated=true + void showBlock(GraphBlock &block, bool animated=false); + void showBlock(GraphBlock *block, bool animated=false); + +protected: + std::unordered_map blocks; + QColor backgroundColor = QColor(Qt::white); + // The vertical margin between blocks + int block_vertical_margin = 32; + + // Zoom data + double current_scale = 1.0; + + void addBlock(GraphView::GraphBlock block); + void setEntry(ut64 e); + void computeGraph(ut64 entry); + + // Callbacks that should be overridden + virtual void drawBlock(QPainter & p, GraphView::GraphBlock &block); + virtual void blockClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos); + virtual void blockTransitionedTo(GraphView::GraphBlock *to); + virtual EdgeConfiguration edgeConfiguration(GraphView::GraphBlock &from, GraphView::GraphBlock *to); + + void adjustSize(int new_width, int new_height); +private: + bool checkPointClicked(QPointF &point, int x, int y, bool above_y=false); + + ut64 entry; + + void computeGraphLayout(GraphBlock &block); + void adjustGraphLayout(GraphBlock &block, int col, int row); + + // Layout type + LayoutType layoutType; + + int width; + int height; + bool ready; + + // Scrolling data + int scroll_base_x; + int scroll_base_y; + bool scroll_mode; + + + // Todo: remove charheight/charwidth cause it should be handled in child class + qreal charWidth = 10.0; + + // Edge computing stuff + template + using Matrix = std::vector>; + using EdgesVector = Matrix>; + std::vector col_edge_x; + std::vector row_edge_y; + bool isEdgeMarked(EdgesVector & edges, int row, int col, int index); + void markEdge(EdgesVector & edges, int row, int col, int index, bool used = true); + int findHorizEdgeIndex(EdgesVector & edges, int row, int min_col, int max_col); + int findVertEdgeIndex(EdgesVector & edges, int col, int min_row, int max_row); + GraphEdge routeEdge(EdgesVector & horiz_edges, EdgesVector & vert_edges, Matrix & edge_valid, GraphBlock &start, GraphBlock &end, QColor color); + +private slots: + void resizeEvent(QResizeEvent* event) override; + // Mouse events + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + +}; + +#endif // GRAPHVIEW_H