diff --git a/src/gui/Src/Gui/DisassemblerGraphView.cpp b/src/gui/Src/Gui/DisassemblerGraphView.cpp index 41b92f19..9605aad1 100644 --- a/src/gui/Src/Gui/DisassemblerGraphView.cpp +++ b/src/gui/Src/Gui/DisassemblerGraphView.cpp @@ -1,740 +1,215 @@ #include "DisassemblerGraphView.h" -#include "MenuBuilder.h" -#include "CachedFontMetrics.h" -#include "QBeaEngine.h" -#include "GotoDialog.h" -#include "XrefBrowseDialog.h" -#include "LineEditDialog.h" -#include "SnowmanView.h" -#include + #include -#include -#include -#include -#include -#include -#include -#include "BreakpointMenu.h" +#include +#include +#include +#include +#include +#include + +#define RVA_INVALID (-1) + +//#include "cutter.h" +//#include "utils/Colors.h" +#include "Configuration.h" +//#include "utils/CachedFontMetrics.h" DisassemblerGraphView::DisassemblerGraphView(QWidget* parent) - : QAbstractScrollArea(parent), - mFontMetrics(nullptr), - currentGraph(duint(0)), - disasm(ConfigUint("Disassembler", "MaxModuleSize")), - mCip(0), - mGoto(nullptr), - syncOrigin(false), - forceCenter(false), - layoutType(LayoutType::Medium), - mHistoryLock(false), - mXrefDlg(nullptr) + : GraphView(parent), + mFontMetrics(nullptr) { - this->status = "Loading..."; + highlight_token = nullptr; + // Signals that require a refresh all + /*connect(Core(), SIGNAL(refreshAll()), this, SLOT(refreshView())); + connect(Core(), SIGNAL(commentsChanged()), this, SLOT(refreshView())); + connect(Core(), SIGNAL(functionRenamed(const QString &, const 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(Core(), SIGNAL(functionsChanged()), this, SLOT(refreshView())); + connect(Core(), SIGNAL(graphOptionsChanged()), this, SLOT(refreshView())); - //Start disassembly view at the entry point of the binary - this->function = 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(); - - //Connect to bridge - 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())); - - //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())); + connect(Core(), SIGNAL(seekChanged(RVA)), this, SLOT(onSeekChanged(RVA))); + // Space to switch to disassembly + QShortcut* shortcut_disassembly = new QShortcut(QKeySequence(Qt::Key_Space), this); + shortcut_disassembly->setContext(Qt::WidgetShortcut); + connect(shortcut_disassembly, &QShortcut::activated, this, [] + { + 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())); + + // Zoom shortcuts + 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())); + QShortcut* shortcut_zoom_out = new QShortcut(QKeySequence(Qt::Key_Minus), this); + shortcut_zoom_out->setContext(Qt::WidgetShortcut); + connect(shortcut_zoom_out, SIGNAL(activated()), this, SLOT(zoomOut())); + QShortcut* shortcut_zoom_reset = new QShortcut(QKeySequence(Qt::Key_Equal), this); + shortcut_zoom_reset->setContext(Qt::WidgetShortcut); + connect(shortcut_zoom_reset, SIGNAL(activated()), this, SLOT(zoomReset())); + + // Branch shortcuts + 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())); + + // Navigation shortcuts + QShortcut* shortcut_next_instr = new QShortcut(QKeySequence(Qt::Key_J), this); + shortcut_next_instr->setContext(Qt::WidgetShortcut); + connect(shortcut_next_instr, SIGNAL(activated()), this, SLOT(nextInstr())); + QShortcut* shortcut_prev_instr = new QShortcut(QKeySequence(Qt::Key_K), this); + shortcut_prev_instr->setContext(Qt::WidgetShortcut); + connect(shortcut_prev_instr, SIGNAL(activated()), this, SLOT(prevInstr())); + shortcuts.append(shortcut_disassembly); + shortcuts.append(shortcut_escape); + shortcuts.append(shortcut_zoom_in); + shortcuts.append(shortcut_zoom_out); + shortcuts.append(shortcut_zoom_reset); + shortcuts.append(shortcut_next_instr); + shortcuts.append(shortcut_prev_instr);*/ + + + initFont(); colorsUpdatedSlot(); } DisassemblerGraphView::~DisassemblerGraphView() { - delete this->highlight_token; } -void DisassemblerGraphView::initFont() +void DisassemblerGraphView::refreshView() { - setFont(ConfigFont("Disassembly")); - 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()); + initFont(); + loadCurrentGraph(); + viewport()->update(); } -void DisassemblerGraphView::adjustSize(int width, int height) +void DisassemblerGraphView::loadCurrentGraph() { - //Recompute size information - this->renderWidth = this->width; - this->renderHeight = this->height; - this->renderXOfs = 0; - this->renderYOfs = 0; - if(this->renderWidth < width) + /*QJsonDocument functionsDoc = Core()->cmdj("agj"); + QJsonArray functions = functionsDoc.array(); + + disassembly_blocks.clear(); + 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) + parentWidget()->setWindowTitle(windowTitle); + + RVA entry = func["offset"].toVariant().toULongLong(); + + setEntry(entry); + for(QJsonValueRef blockRef : func["blocks"].toArray()) { - 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); -} + 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::resizeEvent(QResizeEvent* event) -{ - adjustSize(event->size().width(), event->size().height()); -} - -duint DisassemblerGraphView::get_cursor_pos() -{ - if(this->cur_instr == 0) - return this->function; - return this->cur_instr; -} - -void DisassemblerGraphView::set_cursor_pos(duint 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%p", 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) - { - blockSelected = true; - } + db.false_path = block_fail; + gb.exits.push_back(block_fail); } - - //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)))) + if(block_jump) { - //Render shadow - p.setPen(QColor(0, 0, 0, 0)); - if(block.block.terminal) - p.setBrush(retShadowColor); - else if(block.block.indirectcall) - p.setBrush(indirectcallShadowColor); + if(block_fail) + { + db.true_path = block_jump; + } + gb.exits.push_back(block_jump); + } + 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["disasm"].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); + } + bool cropped; + i.text = Text(RichTextPainter::cropped(richText, Config()->getGraphBlockMaxChars(), "...", &cropped)); + if(cropped) + { + i.fullText = richText; + } 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); - - 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 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; - } + i.fullText = Text(); } + db.instrs.push_back(i); } + disassembly_blocks[db.entry] = db; + prepareGraphNode(gb); + f.blocks.push_back(db); - // Render edges - for(DisassemblerEdge & edge : block.edges) + addBlock(gb); + } + + anal.functions[f.entry] = f; + anal.status = "Ready."; + anal.entry = f.entry; + + if(func["blocks"].toArray().size() > 0) + { + computeGraph(entry); + viewport()->update(); + + if(first_draw) { - 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 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)); - } - - // Draw viewport selection - if(s < 1.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) -{ - /* 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; - } + showBlock(blocks[entry]); + first_draw = false; } }*/ - 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; - - this->viewport()->update(); - - if(event->button() == Qt::RightButton) - { - QMenu wMenu(this); - mMenuBuilder->build(&wMenu); - wMenu.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) -{ - if(drawOverview) - { - toggleOverviewSlot(); - } - else - { - duint instr = this->getInstrForMouseEvent(event); - - //Add address to history - if(!mHistoryLock) - mHistory.addVaToHistory(instr); - - DbgCmdExec(QString("graph dis.branchdest(%1), silent").arg(ToPtrString(instr)).toUtf8().constData()); - } -} - -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) @@ -743,7 +218,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) { @@ -755,1279 +230,464 @@ void DisassemblerGraphView::prepareGraphNode(DisassemblerBlock & block) height += 1; } } - int extra = 4 * this->charWidth + 4; - block.width = width + extra + this->charWidth; - block.height = (height * this->charHeight) + extra; + int extra = 4 * charWidth + 4; + block.width = width + extra + charWidth; + block.height = (height * 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(ConfigFont("Disassembly")); + QFontMetricsF metrics(font()); + baseline = int(metrics.ascent()); + charWidth = metrics.width('X'); + charHeight = metrics.height(); + 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 = 0x1337;//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; - } - - 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.setBrush(retShadowColor); + } + else if(db.indirectcall) + { + p.setBrush(indirectcallShadowColor); } 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; - } - } - block.row = 0; - block.row_count = row_count; -} - -bool DisassemblerGraphView::isEdgeMarked(EdgesVector & edges, int row, int col, int index) -{ - 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) - { - 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++; + p.setBrush(QColor(0, 0, 0, 128)); } - //Mark chosen index as used - for(int col = min_col; col < max_col + 1; col++) - this->markEdge(edges, row, col, i); - return i; -} + p.drawRect(block.x + 4, block.y + 4, + block.width + 4, block.height + 4); + p.setPen(graphNodeColor); -int DisassemblerGraphView::findVertEdgeIndex(EdgesVector & edges, int col, int min_row, int max_row) -{ - //Find a valid index - int i = 0; - while(true) + if(block_selected) { - 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; + p.setBrush(disassemblySelectedBackgroundColor); } else { - min_row = start.row + 1; - max_row = end.row; + p.setBrush(disassemblyBackgroundColor); } - int col = start.col + 1; - if(min_row != max_row) + + p.drawRect(block.x, block.y, + block.width, block.height); + + // Draw different background for selected instruction + if(selected_instruction != RVA_INVALID) { - auto checkColumn = [min_row, max_row, &edge_valid](int column) + int y = block.y + (2 * charWidth) + (db.header_text.lines.size() * charHeight); + for(Instr & instr : db.instrs) { - if(column < 0 || column >= int(edge_valid[min_row].size())) - return false; - for(int row = min_row; row < max_row; row++) + auto selected = instr.addr == selected_instruction; + //auto traceCount = dbgfunctions->GetTraceRecordHitCount(instr.addr); + auto traceCount = 0; + if(selected && traceCount) { - if(!edge_valid[row][column]) - { - return false; - } + p.fillRect(QRect(block.x + charWidth, y, block.width - (10 + 2 * charWidth), + int(instr.text.lines.size()) * charHeight), disassemblyTracedSelectionColor); } - return true; - }; - - if(!checkColumn(col)) - { - if(checkColumn(end.col + 1)) + else if(selected) { - col = end.col + 1; + p.fillRect(QRect(block.x + charWidth, y, block.width - (10 + 2 * charWidth), + int(instr.text.lines.size()) * charHeight), disassemblySelectionColor); } - else + else if(traceCount) { - int ofs = 0; - while(true) - { - col = start.col + 1 - ofs; - if(checkColumn(col)) - { - break; - } + // 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; - col = start.col + 1 + ofs; - if(checkColumn(col)) - { - break; - } + // If the user has a light trace background color, substract + if(disassemblyTracedColor.blue() > 160) + colorDiff *= -1; - ofs += 1; - } + p.fillRect(QRect(block.x + charWidth, y, block.width - (10 + 2 * charWidth), int(instr.text.lines.size()) * charHeight), + QColor(disassemblyTracedColor.red(), + disassemblyTracedColor.green(), + std::max(0, std::min(256, disassemblyTracedColor.blue() + colorDiff)))); } + y += int(instr.text.lines.size()) * charHeight; } } - if(col != (start.col + 1)) + + // Render node text + auto x = block.x + (2 * charWidth); + int y = block.y + (2 * charWidth); + for(auto & line : db.header_text.lines) { - //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; + RichTextPainter::paintRichText(&p, x, y, block.width, charHeight, 0, line, mFontMetrics); + y += charHeight; } - - if(end.row != (start.row + 1)) + for(Instr & instr : db.instrs) { - //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)) + for(auto & line : instr.text.lines) { - 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; - } + int rectSize = qRound(charWidth); + if(rectSize % 2) + { + rectSize++; + } + // Assume charWidth <= charHeight + QRectF bpRect(x - rectSize / 3.0, y + (charHeight - rectSize) / 2.0, rectSize, rectSize); - //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; - } + // TODO: Breakpoint/Cip stuff - return edge; + RichTextPainter::paintRichText(&p, x + charWidth, y, block.width - charWidth, charHeight, 0, line, mFontMetrics); + y += charHeight; + + } + } } -template -static void removeFromVec(std::vector & vec, T elem) +GraphView::EdgeConfiguration DisassemblerGraphView::edgeConfiguration(GraphView::GraphBlock & from, GraphView::GraphBlock* to) { - 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) -{ - //puts("Starting renderFunction"); - - //Create render nodes - this->blocks.clear(); - for(Block & block : func.blocks) + EdgeConfiguration ec; + DisassemblyBlock & db = disassembly_blocks[from.entry]; + if(to->entry == db.true_path) { - this->blocks[block.entry] = DisassemblerBlock(block); - this->prepareGraphNode(this->blocks[block.entry]); + ec.color = brtrueColor; } - //puts("Create render nodes"); - - //Populate incoming lists - for(auto & blockIt : this->blocks) + else if(to->entry == db.false_path) { - 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; + ec.color = brfalseColor; } 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); + ec.color = jmpColor; } - - this->ready = true; - this->viewport()->update(0, 0, areaSize.width(), areaSize.height()); - //puts("Finished"); + ec.start_arrow = false; + ec.end_arrow = true; + return ec; } -void DisassemblerGraphView::show_cur_instr(bool force) +RVA DisassemblerGraphView::getAddrForMouseEvent(GraphBlock & block, QPoint* point) { - for(auto & blockIt : this->blocks) + DisassemblyBlock & db = disassembly_blocks[block.entry]; + + // Remove header and margin + int off_y = (2 * charWidth) + (db.header_text.lines.size() * charHeight); + // Get mouse coordinate over the actual text + int text_point_y = point->y() - off_y; + int mouse_row = text_point_y / charHeight; + + int cur_row = db.header_text.lines.size(); + if(mouse_row < cur_row) { - DisassemblerBlock & block = blockIt.second; - auto row = int(block.block.header_text.lines.size()); - for(Instr & instr : block.block.instrs) + return db.entry; + } + + Instr* instr = getInstrForMouseEvent(block, point); + if(instr) + { + return instr->addr; + } + + return RVA_INVALID; +} + + +DisassemblerGraphView::Instr* DisassemblerGraphView::getInstrForMouseEvent(GraphView::GraphBlock & block, QPoint* point) +{ + DisassemblyBlock & db = disassembly_blocks[block.entry]; + + // Remove header and margin + int off_y = (2 * charWidth) + (db.header_text.lines.size() * charHeight); + // Get mouse coordinate over the actual text + int text_point_y = point->y() - off_y; + int mouse_row = text_point_y / charHeight; + + int cur_row = db.header_text.lines.size(); + + for(Instr & instr : db.instrs) + { + if(mouse_row < cur_row + (int)instr.text.lines.size()) { - 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()); + return &instr; } + cur_row += 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; + return nullptr; } -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() -{ - bool showGraphRva = ConfigBool("Gui", "ShowGraphRva"); - Analysis anal; - anal.entry = currentGraph.entryPoint; - anal.ready = true; - { - Function func; - func.entry = currentGraph.entryPoint; - func.ready = true; - { - for(const auto & nodeIt : currentGraph.nodes) - { - const BridgeCFNode & node = nodeIt.second; - Block block; - block.entry = node.instrs.empty() ? node.start : node.instrs[0].addr; - block.exits = node.exits; - block.false_path = node.brfalse; - block.true_path = node.brtrue; - block.terminal = node.terminal; - block.indirectcall = node.indirectcall; - block.header_text = Text(getSymbolicName(block.entry), mLabelColor, mLabelBackgroundColor); - { - Instr instr; - for(const BridgeCFInstruction & nodeInstr : node.instrs) - { - auto addr = nodeInstr.addr; - currentBlockMap[addr] = block.entry; - Instruction_t instrTok = disasm.DisassembleAt((byte_t*)nodeInstr.data, sizeof(nodeInstr.data), 0, addr, false); - RichTextPainter::List richText; - CapstoneTokenizer::TokenToRichText(instrTok.tokens, richText, 0); - - // add rva to node instruction text - if(showGraphRva) - { - RichTextPainter::CustomRichText_t rvaText; - rvaText.highlight = false; - rvaText.textColor = mAddressColor; - rvaText.textBackground = mAddressBackgroundColor; - rvaText.text = QString().number(instrTok.rva, 16).toUpper().trimmed() + " "; - rvaText.flags = rvaText.textBackground.alpha() ? RichTextPainter::FlagAll : RichTextPainter::FlagColor; - richText.insert(richText.begin(), rvaText); - } - - auto size = instrTok.length; - instr.addr = addr; - instr.opcode.resize(size); - for(int j = 0; j < size; j++) - instr.opcode[j] = nodeInstr.data[j]; - - QString comment; - bool autoComment = false; - RichTextPainter::CustomRichText_t commentText; - commentText.highlight = false; - char label[MAX_LABEL_SIZE] = ""; - if(GetCommentFormat(addr, comment, &autoComment)) - { - if(autoComment) - { - commentText.textColor = mAutoCommentColor; - commentText.textBackground = mAutoCommentBackgroundColor; - } - else //user comment - { - commentText.textColor = mCommentColor; - commentText.textBackground = mCommentBackgroundColor; - } - commentText.text = QString("; ") + comment; - //add to text - } - else if(DbgGetLabelAt(addr, SEG_DEFAULT, label) && addr != block.entry) // label but no comment - { - commentText.textColor = mLabelColor; - commentText.textBackground = mLabelBackgroundColor; - commentText.text = QString("; ") + label; - } - commentText.flags = commentText.textBackground.alpha() ? RichTextPainter::FlagAll : RichTextPainter::FlagColor; - if(commentText.text.length()) - { - RichTextPainter::CustomRichText_t spaceText; - spaceText.highlight = false; - spaceText.flags = RichTextPainter::FlagNone; - spaceText.text = " "; - richText.push_back(spaceText); - richText.push_back(commentText); - } - instr.text = Text(richText); - - //The summary contains calls, rets, user comments and string references - if(!onlySummary || - instrTok.branchType == Instruction_t::Call || - instrTok.instStr.startsWith("ret", Qt::CaseInsensitive) || - (!commentText.text.isEmpty() && !autoComment) || - commentText.text.contains('\"')) - block.instrs.push_back(instr); - } - } - func.blocks.push_back(block); - } - } - anal.functions.insert({func.entry, func}); - } - this->analysis = anal; - this->function = this->analysis.entry; - this->renderFunction(this->analysis.functions[this->function]); -} - -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::graphAtSlot(duint addr) -{ - Bridge::getBridge()->setResult(this->navigate(addr) ? this->currentGraph.entryPoint : 0); -} - -void DisassemblerGraphView::updateGraphSlot() -{ - this->viewport()->update(); -} - -void DisassemblerGraphView::addReferenceAction(QMenu* menu, duint 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() -{ - mMenuBuilder = new MenuBuilder(this, [](QMenu*) - { - return DbgIsDebugging(); - }); - - mMenuBuilder->addAction(makeShortcutAction(DIcon(QString("processor%1.png").arg(ArchValue("32", "64"))), tr("Follow in &Disassembler"), SLOT(followDisassemblerSlot()), "ActionGraphFollowDisassembler"), [this](QMenu*) - { - return this->cur_instr != 0; - }); - mMenuBuilder->addSeparator(); - - auto breakpointMenu = new BreakpointMenu(this, getActionHelperFuncs(), [this]() - { - return cur_instr; - }); - breakpointMenu->build(mMenuBuilder); - mMenuBuilder->addAction(makeShortcutAction(DIcon("comment.png"), tr("&Comment"), SLOT(setCommentSlot()), "ActionSetComment")); - mMenuBuilder->addAction(makeShortcutAction(DIcon("label.png"), tr("&Label"), SLOT(setLabelSlot()), "ActionSetLabel")); - MenuBuilder* gotoMenu = new MenuBuilder(this); - gotoMenu->addAction(makeShortcutAction(DIcon("geolocation-goto.png"), tr("Expression"), SLOT(gotoExpressionSlot()), "ActionGotoExpression")); - gotoMenu->addAction(makeShortcutAction(DIcon("cbp.png"), tr("Origin"), SLOT(gotoOriginSlot()), "ActionGotoOrigin")); - gotoMenu->addAction(makeShortcutAction(DIcon("previous.png"), tr("Previous"), SLOT(gotoPreviousSlot()), "ActionGotoPrevious"), [this](QMenu*) - { - return mHistory.historyHasPrev(); - }); - gotoMenu->addAction(makeShortcutAction(DIcon("next.png"), tr("Next"), SLOT(gotoNextSlot()), "ActionGotoNext"), [this](QMenu*) - { - return mHistory.historyHasNext(); - }); - MenuBuilder* childrenAndParentMenu = new MenuBuilder(this, [this](QMenu * menu) - { - duint cursorpos = get_cursor_pos(); - const DisassemblerBlock* currentBlock = nullptr; - const Instr* currentInstruction = nullptr; - for(const auto & i : blocks) - { - if(i.second.block.entry > cursorpos) - continue; - for(const Instr & inst : i.second.block.instrs) - { - if(inst.addr <= cursorpos && inst.addr + inst.opcode.size() > cursorpos) - { - currentBlock = &i.second; - currentInstruction = &inst; - break; - } - } - if(currentInstruction) - break; - } - if(currentInstruction) - { - for(const duint & i : currentBlock->incoming) // This list is incomplete - addReferenceAction(menu, i); - if(!currentBlock->block.terminal) - { - menu->addSeparator(); - for(const duint & i : currentBlock->block.exits) - addReferenceAction(menu, i); - } - //to do: follow a constant - return true; - } - return false; - }); - gotoMenu->addSeparator(); - gotoMenu->addBuilder(childrenAndParentMenu); - mMenuBuilder->addMenu(makeMenu(DIcon("goto.png"), tr("Go to")), gotoMenu); - mMenuBuilder->addAction(makeShortcutAction(DIcon("xrefs.png"), tr("Xrefs..."), SLOT(xrefSlot()), "ActionXrefs")); - mMenuBuilder->addAction(makeShortcutAction(DIcon("snowman.png"), tr("Decompile"), SLOT(decompileSlot()), "ActionGraphDecompile")); - mMenuBuilder->addSeparator(); - mMenuBuilder->addAction(mToggleOverview = makeShortcutAction(DIcon("graph.png"), tr("&Overview"), SLOT(toggleOverviewSlot()), "ActionGraphToggleOverview")); - mToggleOverview->setCheckable(true); - mMenuBuilder->addAction(mToggleSummary = makeShortcutAction(DIcon("summary.png"), tr("S&ummary"), SLOT(toggleSummarySlot()), "ActionGraphToggleSummary")); - mToggleSummary->setCheckable(true); - mMenuBuilder->addAction(mToggleSyncOrigin = makeShortcutAction(DIcon("lock.png"), tr("&Sync with origin"), SLOT(toggleSyncOriginSlot()), "ActionGraphSyncOrigin")); - mMenuBuilder->addAction(makeShortcutAction(DIcon("sync.png"), tr("&Refresh"), SLOT(refreshSlot()), "ActionRefresh")); - mMenuBuilder->addAction(makeShortcutAction(DIcon("image.png"), tr("&Save as image"), SLOT(saveImageSlot()), "ActionGraphSaveImage")); - - MenuBuilder* layoutMenu = new MenuBuilder(this); - QActionGroup* layoutGroup = new QActionGroup(this); - layoutGroup->addAction(makeAction(DIcon("narrow.png"), tr("Narrow"), [this]() { setGraphLayout(LayoutType::Narrow); })); - QAction* mediumLayout = - layoutGroup->addAction(makeAction(DIcon("medium.png"), tr("Medium"), [this]() { setGraphLayout(LayoutType::Medium); })); - layoutGroup->addAction(makeAction(DIcon("wide.png"), tr("Wide"), [this]() { setGraphLayout(LayoutType::Wide); })); - for(QAction* layoutAction : layoutGroup->actions()) - { - layoutAction->setCheckable(true); - layoutMenu->addAction(layoutAction); - } - mediumLayout->setChecked(true); - mMenuBuilder->addMenu(makeMenu(DIcon("layout.png"), tr("Layout")), layoutMenu); - - mMenuBuilder->loadFromConfig(); -} - -void DisassemblerGraphView::keyPressEvent(QKeyEvent* 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()); - else if(key == Qt::Key_Return || key == Qt::Key_Enter) - { - //Add address to history - if(!mHistoryLock) - mHistory.addVaToHistory(cur_instr); - 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("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"); + disassemblyBackgroundColor = ConfigColor("gui.alt_background"); + disassemblySelectedBackgroundColor = ConfigColor("gui.background"); + mDisabledBreakpointColor = disassemblyBackgroundColor; + graphNodeColor = ConfigColor("gui.border"); + backgroundColor = ConfigColor("gui.background"); + disassemblySelectionColor = ConfigColor("highlight"); - 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"); + jmpColor = ConfigColor("graph.trufae"); + brtrueColor = ConfigColor("graph.true"); + brfalseColor = ConfigColor("graph.false"); - fontChanged(); - loadCurrentGraph(); + mCommentColor = ConfigColor("comment"); + initFont(); + refreshView(); } void DisassemblerGraphView::fontsUpdatedSlot() { - fontChanged(); + 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); - mGoto->setInitialExpression(ToPtrString(this->cur_instr)); - 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()); - } -} - -void DisassemblerGraphView::gotoOriginSlot() -{ - 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; - this->viewport()->update(); -} - -void DisassemblerGraphView::setCommentSlot() -{ - 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)); + 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 - mLineEdit.setText(QString(comment_text)); + { + 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::zoomIn() +{ + current_scale += 0.1; + auto areaSize = viewport()->size(); + adjustSize(areaSize.width(), areaSize.height()); + viewport()->update(); +} + +void DisassemblerGraphView::zoomOut() +{ + current_scale -= 0.1; + current_scale = std::max(current_scale, 0.3); + auto areaSize = viewport()->size(); + adjustSize(areaSize.width(), areaSize.height()); + viewport()->update(); +} + +void DisassemblerGraphView::zoomReset() +{ + current_scale = 1.0; + auto areaSize = viewport()->size(); + adjustSize(areaSize.width(), areaSize.height()); + viewport()->update(); +} + +void DisassemblerGraphView::takeTrue() +{ + /*DisassemblyBlock* db = blockForAddress(Core()->getOffset()); + if(db->true_path != RVA_INVALID) + { + Core()->seek(db->true_path); + } + else if(blocks[db->entry].exits.size()) + { + Core()->seek(blocks[db->entry].exits[0]); + }*/ +} + +void DisassemblerGraphView::takeFalse() +{ + /*DisassemblyBlock* db = blockForAddress(Core()->getOffset()); + if(db->false_path != RVA_INVALID) + { + Core()->seek(db->false_path); + } + else if(blocks[db->entry].exits.size()) + { + Core()->seek(blocks[db->entry].exits[0]); + }*/ +} + +void DisassemblerGraphView::seekInstruction(bool previous_instr) +{ + RVA addr = 0x1337;//Core()->getOffset(); + DisassemblyBlock* db = blockForAddress(addr); + if(!db) + { + return; } - 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(); -} - -void DisassemblerGraphView::setLabelSlot() -{ - 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) + for(size_t i = 0; i < db->instrs.size(); i++) { - 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; - } - if(!DbgSetLabelAt(wVA, utf8data.constData())) - SimpleErrorBox(this, tr("Error!"), tr("DbgSetLabelAt failed!")); + Instr & instr = db->instrs[i]; + if(!((instr.addr <= addr) && (addr <= instr.addr + instr.size))) + { + continue; + } - this->refreshSlot(); -} - -void DisassemblerGraphView::xrefSlot() -{ - - if(!DbgIsDebugging()) - return; - duint wVA = this->get_cursor_pos(); - if(!DbgMemIsValidReadPtr(wVA)) - return; - XREF_INFO mXrefInfo; - DbgXrefGet(wVA, &mXrefInfo); - if(!mXrefInfo.refcount) - return; - BridgeFree(mXrefInfo.references); - if(!mXrefDlg) - mXrefDlg = new XrefBrowseDialog(this); - mXrefDlg->setup(wVA, "graph"); - mXrefDlg->showNormal(); -} - -void DisassemblerGraphView::decompileSlot() -{ - std::vector ranges; - ranges.reserve(currentGraph.nodes.size()); - - if(!DbgIsDebugging()) - return; - if(currentGraph.nodes.empty()) - return; - SnowmanRange r; - for(const auto & nodeIt : currentGraph.nodes) - { - 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); - } - std::sort(ranges.begin(), ranges.end(), [](const SnowmanRange & a, const SnowmanRange & b) - { - return a.start > b.start; - }); - emit displaySnowmanWidget(); - DecompileRanges(Bridge::getBridge()->snowmanView, ranges.data(), ranges.size()); -} - -void DisassemblerGraphView::followActionSlot() -{ - QAction* action = qobject_cast(sender()); - if(action) - { - QString data = action->data().toString(); - DbgCmdExecDirect(QString("graph %1, silent").arg(data).toUtf8().constData()); + // Found the instructon. Check if a next one exists + if(!previous_instr && (i < db->instrs.size() - 1)) + { + seek(db->instrs[i + 1].addr, true); + } + else if(previous_instr && (i > 0)) + { + seek(db->instrs[i - 1].addr); + } } } + +void DisassemblerGraphView::nextInstr() +{ + seekInstruction(false); +} + +void DisassemblerGraphView::prevInstr() +{ + seekInstruction(true); +} + +void DisassemblerGraphView::seek(RVA addr, bool update_viewport) +{ + sent_seek = true; + //Core()->seek(addr); + if(update_viewport) + { + viewport()->update(); + } +} + +void DisassemblerGraphView::seekPrev() +{ + //Core()->seekPrev(); +} + +void DisassemblerGraphView::blockClicked(GraphView::GraphBlock & block, QMouseEvent* event, QPoint pos) +{ + RVA instr = getAddrForMouseEvent(block, &pos); + if(instr == RVA_INVALID) + { + return; + } + + seek(instr, true); + + if(event->button() == Qt::RightButton) + { + //mMenu->setOffset(instr); + //mMenu->exec(event->globalPos()); + } +} + +void DisassemblerGraphView::blockDoubleClicked(GraphView::GraphBlock & block, QMouseEvent* event, QPoint pos) +{ + Q_UNUSED(event); + RVA instr = getAddrForMouseEvent(block, &pos); + if(instr == RVA_INVALID) + { + return; + } + /*QList refs = Core()->getXRefs(instr, false, false); + if(refs.length()) + { + sent_seek = false; + Core()->seek(refs.at(0).to); + } + if(refs.length() > 1) + { + qWarning() << "Too many references here. Weird behaviour expected."; + }*/ +} + +void DisassemblerGraphView::blockHelpEvent(GraphView::GraphBlock & block, QHelpEvent* event, QPoint pos) +{ + Instr* instr = getInstrForMouseEvent(block, &pos); + if(!instr || instr->fullText.lines.empty()) + { + QToolTip::hideText(); + event->ignore(); + return; + } + + QToolTip::showText(event->globalPos(), instr->fullText.ToQString()); +} + +bool DisassemblerGraphView::helpEvent(QHelpEvent* event) +{ + if(!GraphView::helpEvent(event)) + { + QToolTip::hideText(); + event->ignore(); + } + + return true; +} + +void DisassemblerGraphView::blockTransitionedTo(GraphView::GraphBlock* to) +{ + if(transition_dont_seek) + { + transition_dont_seek = false; + return; + } + seek(to->entry); +} diff --git a/src/gui/Src/Gui/DisassemblerGraphView.h b/src/gui/Src/Gui/DisassemblerGraphView.h index 7b9598e4..36a6421a 100644 --- a/src/gui/Src/Gui/DisassemblerGraphView.h +++ b/src/gui/Src/Gui/DisassemblerGraphView.h @@ -1,74 +1,36 @@ #ifndef DISASSEMBLERGRAPHVIEW_H #define DISASSEMBLERGRAPHVIEW_H -#include +// Based on the DisassemblerGraphView from x64dbg + #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "Bridge.h" +#include +#include + +#include "GraphView.h" #include "RichTextPainter.h" -#include "QBeaEngine.h" -#include "ActionHelpers.h" -#include "VaHistory.h" +#include "CachedFontMetrics.h" -class MenuBuilder; -class CachedFontMetrics; -class GotoDialog; -class XrefBrowseDialog; +using RVA = size_t; -class DisassemblerGraphView : public QAbstractScrollArea, public ActionHelper +class DisassemblerGraphView : public GraphView { 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] + ut64 addr; //token[3] QString name; //token[4] }; struct HighlightToken { QString type; //highlight_token[0] - duint addr; //highlight_token[1] + ut64 addr; //highlight_token[1] QString name; //highlight_token[2] bool equalsToken(const Token & token) @@ -123,65 +85,48 @@ public: result += t.text; } } - return std::move(result); + return result; } }; struct Instr { - duint addr = 0; + ut64 addr = 0; + ut64 size = 0; Text text; + Text fullText; std::vector opcode; //instruction bytes }; - struct Block + + struct DisassemblyBlock { Text header_text; std::vector instrs; - std::vector exits; - duint entry = 0; - duint true_path = 0; - duint false_path = 0; + ut64 entry = 0; + ut64 true_path = 0; + ut64 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; - std::vector blocks; + ut64 entry; + ut64 update_id; + std::vector blocks; }; struct Analysis { - duint entry = 0; - std::unordered_map functions; + ut64 entry = 0; + std::unordered_map functions; bool ready = false; + ut64 update_id = 0; QString status = "Analyzing..."; - bool find_instr(duint addr, duint & func, duint & instr) + bool find_instr(ut64 addr, ut64 & func, ut64 & instr) { //TODO implement Q_UNUSED(addr); @@ -193,132 +138,67 @@ public: //dummy class }; - enum class LayoutType - { - Wide, - Medium, - Narrow, - }; - - DisassemblerGraphView(QWidget* parent = nullptr); +public: + 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); + 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 void blockDoubleClicked(GraphView::GraphBlock & block, QMouseEvent* event, QPoint pos) override; + virtual bool helpEvent(QHelpEvent* event) override; + virtual void blockHelpEvent(GraphView::GraphBlock & block, QHelpEvent* event, QPoint pos) override; + virtual GraphView::EdgeConfiguration edgeConfiguration(GraphView::GraphBlock & from, GraphView::GraphBlock* to) override; + virtual void blockTransitionedTo(GraphView::GraphBlock* to) override; - 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 loadCurrentGraph(); + // bool navigate(ut64 addr); public slots: - void loadGraphSlot(BridgeCFGraphList* graph, duint addr); - void graphAtSlot(duint addr); - void updateGraphSlot(); - void followDisassemblerSlot(); + void refreshView(); 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 xrefSlot(); - void decompileSlot(); + void onSeekChanged(RVA addr); + + void zoomIn(); + void zoomOut(); + void zoomReset(); + + void takeTrue(); + void takeFalse(); + + void nextInstr(); + void prevInstr(); + +private slots: + void seekPrev(); private: - QString status; - Analysis analysis; - duint function; - QTimer* updateTimer; - int baseline; + bool first_draw = true; + bool transition_dont_seek = false; + bool sent_seek = false; + + HighlightToken* highlight_token; + // Font data + CachedFontMetrics* mFontMetrics; 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; - 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; + int baseline; - QAction* mToggleOverview; - QAction* mToggleSummary; - QAction* mToggleSyncOrigin; + //DisassemblyContextMenu* mMenu; + + void initFont(); + void prepareGraphNode(GraphBlock & block); + RVA getAddrForMouseEvent(GraphBlock & block, QPoint* point); + Instr* getInstrForMouseEvent(GraphBlock & block, QPoint* point); + DisassemblyBlock* blockForAddress(RVA addr); + void seek(RVA addr, bool update_viewport = true); + void seekInstruction(bool previous_instr); + + //QList shortcuts; QColor disassemblyBackgroundColor; + QColor disassemblySelectedBackgroundColor; QColor disassemblySelectionColor; QColor disassemblyTracedColor; QColor disassemblyTracedSelectionColor; @@ -327,7 +207,6 @@ private: QColor brfalseColor; QColor retShadowColor; QColor indirectcallShadowColor; - QColor backgroundColor; QColor mAutoCommentColor; QColor mAutoCommentBackgroundColor; QColor mCommentColor; @@ -340,14 +219,6 @@ private: QColor mCipColor; QColor mBreakpointColor; QColor mDisabledBreakpointColor; - - BridgeCFGraph currentGraph; - std::unordered_map currentBlockMap; - QBeaEngine disasm; - GotoDialog* mGoto; - XrefBrowseDialog* mXrefDlg; - - void addReferenceAction(QMenu* menu, duint addr); }; #endif // DISASSEMBLERGRAPHVIEW_H diff --git a/src/gui/Src/Gui/DisassemblerGraphViewOld.cpp b/src/gui/Src/Gui/DisassemblerGraphViewOld.cpp new file mode 100644 index 00000000..287929de --- /dev/null +++ b/src/gui/Src/Gui/DisassemblerGraphViewOld.cpp @@ -0,0 +1,2033 @@ +#include "DisassemblerGraphViewOld.h" +#include "MenuBuilder.h" +#include "CachedFontMetrics.h" +#include "QBeaEngine.h" +#include "GotoDialog.h" +#include "XrefBrowseDialog.h" +#include "LineEditDialog.h" +#include "SnowmanView.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "BreakpointMenu.h" + +DisassemblerGraphViewOld::DisassemblerGraphViewOld(QWidget* parent) + : QAbstractScrollArea(parent), + mFontMetrics(nullptr), + currentGraph(duint(0)), + disasm(ConfigUint("Disassembler", "MaxModuleSize")), + mCip(0), + mGoto(nullptr), + syncOrigin(false), + forceCenter(false), + layoutType(LayoutType::Medium), + mHistoryLock(false), + mXrefDlg(nullptr) +{ + this->status = "Loading..."; + + //Start disassembly view at the entry point of the binary + this->function = 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(); + + //Connect to bridge + 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())); + + //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())); + + colorsUpdatedSlot(); +} + +DisassemblerGraphViewOld::~DisassemblerGraphViewOld() +{ + delete this->highlight_token; +} + +void DisassemblerGraphViewOld::initFont() +{ + setFont(ConfigFont("Disassembly")); + 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 DisassemblerGraphViewOld::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) + { + this->renderXOfs = (width - this->renderWidth) / 2; + this->renderWidth = width; + } + 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); +} + +void DisassemblerGraphViewOld::resizeEvent(QResizeEvent* event) +{ + adjustSize(event->size().width(), event->size().height()); +} + +duint DisassemblerGraphViewOld::get_cursor_pos() +{ + if(this->cur_instr == 0) + return this->function; + return this->cur_instr; +} + +void DisassemblerGraphViewOld::set_cursor_pos(duint addr) +{ + if(!this->navigate(addr)) + { + //TODO: show in hex editor? + } +} + +std::tuple DisassemblerGraphViewOld::get_selection_range() +{ + return std::make_tuple(get_cursor_pos(), get_cursor_pos()); +} + +void DisassemblerGraphViewOld::set_selection_range(std::tuple range) +{ + this->set_cursor_pos(std::get<0>(range)); +} + +void DisassemblerGraphViewOld::copy_address() +{ + QClipboard* clipboard = QApplication::clipboard(); + clipboard->clear(); + QMimeData mime; + mime.setText(QString().sprintf("0x%p", this->get_cursor_pos())); + clipboard->setMimeData(&mime); +} + +void DisassemblerGraphViewOld::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) + { + if(instr.addr == this->cur_instr) + { + blockSelected = true; + } + } + + //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); + + 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 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; + } + } + } + + // 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 DisassemblerGraphViewOld::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 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)); + } + + // Draw viewport selection + if(s < 1.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 DisassemblerGraphViewOld::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 DisassemblerGraphViewOld::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 DisassemblerGraphViewOld::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 DisassemblerGraphViewOld::getTokenForMouseEvent(QMouseEvent* event, Token & 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 DisassemblerGraphViewOld::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 DisassemblerGraphViewOld::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; + + this->viewport()->update(); + + if(event->button() == Qt::RightButton) + { + QMenu wMenu(this); + mMenuBuilder->build(&wMenu); + wMenu.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 DisassemblerGraphViewOld::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 DisassemblerGraphViewOld::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 DisassemblerGraphViewOld::mouseDoubleClickEvent(QMouseEvent* event) +{ + if(drawOverview) + { + toggleOverviewSlot(); + } + else + { + duint instr = this->getInstrForMouseEvent(event); + + //Add address to history + if(!mHistoryLock) + mHistory.addVaToHistory(instr); + + DbgCmdExec(QString("graph dis.branchdest(%1), silent").arg(ToPtrString(instr)).toUtf8().constData()); + } +} + +void DisassemblerGraphViewOld::prepareGraphNode(DisassemblerBlock & block) +{ + int width = 0; + int height = 0; + for(auto & line : block.block.header_text.lines) + { + int lw = 0; + for(auto & part : line) + lw += mFontMetrics->width(part.text); + if(lw > width) + width = lw; + height += 1; + } + for(Instr & instr : block.block.instrs) + { + for(auto & line : instr.text.lines) + { + int lw = 0; + for(auto & part : line) + lw += mFontMetrics->width(part.text); + if(lw > width) + width = lw; + height += 1; + } + } + int extra = 4 * this->charWidth + 4; + block.width = width + extra + this->charWidth; + block.height = (height * this->charHeight) + extra; +} + +void DisassemblerGraphViewOld::adjustGraphLayout(DisassemblerBlock & block, int col, int row) +{ + block.col += col; + block.row += row; + for(duint edge : block.new_exits) + this->adjustGraphLayout(this->blocks[edge], col, row); +} + +void DisassemblerGraphViewOld::computeGraphLayout(DisassemblerBlock & 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++) + { + 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; + } + + if(this->layoutType != LayoutType::Wide && block.new_exits.size() == 2) + { + 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; + } + + 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(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; + } + } + block.row = 0; + block.row_count = row_count; +} + +bool DisassemblerGraphViewOld::isEdgeMarked(EdgesVector & edges, int row, int col, int index) +{ + if(index >= int(edges[row][col].size())) + return false; + return edges[row][col][index]; +} + +void DisassemblerGraphViewOld::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 DisassemblerGraphViewOld::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 DisassemblerGraphViewOld::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; +} + +DisassemblerGraphViewOld::DisassemblerEdge DisassemblerGraphViewOld::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(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; +} + +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 DisassemblerGraphViewOld::renderFunction(Function & func) +{ + //puts("Starting renderFunction"); + + //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->ready = true; + this->viewport()->update(0, 0, areaSize.width(), areaSize.height()); + //puts("Finished"); +} + +void DisassemblerGraphViewOld::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 DisassemblerGraphViewOld::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 DisassemblerGraphViewOld::fontChanged() +{ + this->initFont(); + + if(this->ready) + { + //Rerender function to update layout + this->renderFunction(this->analysis.functions[this->function]); + } +} + +void DisassemblerGraphViewOld::setGraphLayout(DisassemblerGraphViewOld::LayoutType layout) +{ + this->layoutType = layout; + if(this->ready) + { + this->renderFunction(this->analysis.functions[this->function]); + } +} + +void DisassemblerGraphViewOld::tokenizerConfigUpdatedSlot() +{ + disasm.UpdateConfig(); + loadCurrentGraph(); +} + +void DisassemblerGraphViewOld::loadCurrentGraph() +{ + bool showGraphRva = ConfigBool("Gui", "ShowGraphRva"); + Analysis anal; + anal.entry = currentGraph.entryPoint; + anal.ready = true; + { + Function func; + func.entry = currentGraph.entryPoint; + func.ready = true; + { + for(const auto & nodeIt : currentGraph.nodes) + { + const BridgeCFNode & node = nodeIt.second; + Block block; + block.entry = node.instrs.empty() ? node.start : node.instrs[0].addr; + block.exits = node.exits; + block.false_path = node.brfalse; + block.true_path = node.brtrue; + block.terminal = node.terminal; + block.indirectcall = node.indirectcall; + block.header_text = Text(getSymbolicName(block.entry), mLabelColor, mLabelBackgroundColor); + { + Instr instr; + for(const BridgeCFInstruction & nodeInstr : node.instrs) + { + auto addr = nodeInstr.addr; + currentBlockMap[addr] = block.entry; + Instruction_t instrTok = disasm.DisassembleAt((byte_t*)nodeInstr.data, sizeof(nodeInstr.data), 0, addr, false); + RichTextPainter::List richText; + CapstoneTokenizer::TokenToRichText(instrTok.tokens, richText, 0); + + // add rva to node instruction text + if(showGraphRva) + { + RichTextPainter::CustomRichText_t rvaText; + rvaText.highlight = false; + rvaText.textColor = mAddressColor; + rvaText.textBackground = mAddressBackgroundColor; + rvaText.text = QString().number(instrTok.rva, 16).toUpper().trimmed() + " "; + rvaText.flags = rvaText.textBackground.alpha() ? RichTextPainter::FlagAll : RichTextPainter::FlagColor; + richText.insert(richText.begin(), rvaText); + } + + auto size = instrTok.length; + instr.addr = addr; + instr.opcode.resize(size); + for(int j = 0; j < size; j++) + instr.opcode[j] = nodeInstr.data[j]; + + QString comment; + bool autoComment = false; + RichTextPainter::CustomRichText_t commentText; + commentText.highlight = false; + char label[MAX_LABEL_SIZE] = ""; + if(GetCommentFormat(addr, comment, &autoComment)) + { + if(autoComment) + { + commentText.textColor = mAutoCommentColor; + commentText.textBackground = mAutoCommentBackgroundColor; + } + else //user comment + { + commentText.textColor = mCommentColor; + commentText.textBackground = mCommentBackgroundColor; + } + commentText.text = QString("; ") + comment; + //add to text + } + else if(DbgGetLabelAt(addr, SEG_DEFAULT, label) && addr != block.entry) // label but no comment + { + commentText.textColor = mLabelColor; + commentText.textBackground = mLabelBackgroundColor; + commentText.text = QString("; ") + label; + } + commentText.flags = commentText.textBackground.alpha() ? RichTextPainter::FlagAll : RichTextPainter::FlagColor; + if(commentText.text.length()) + { + RichTextPainter::CustomRichText_t spaceText; + spaceText.highlight = false; + spaceText.flags = RichTextPainter::FlagNone; + spaceText.text = " "; + richText.push_back(spaceText); + richText.push_back(commentText); + } + instr.text = Text(richText); + + //The summary contains calls, rets, user comments and string references + if(!onlySummary || + instrTok.branchType == Instruction_t::Call || + instrTok.instStr.startsWith("ret", Qt::CaseInsensitive) || + (!commentText.text.isEmpty() && !autoComment) || + commentText.text.contains('\"')) + block.instrs.push_back(instr); + } + } + func.blocks.push_back(block); + } + } + anal.functions.insert({func.entry, func}); + } + this->analysis = anal; + this->function = this->analysis.entry; + this->renderFunction(this->analysis.functions[this->function]); +} + +void DisassemblerGraphViewOld::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 DisassemblerGraphViewOld::graphAtSlot(duint addr) +{ + Bridge::getBridge()->setResult(this->navigate(addr) ? this->currentGraph.entryPoint : 0); +} + +void DisassemblerGraphViewOld::updateGraphSlot() +{ + this->viewport()->update(); +} + +void DisassemblerGraphViewOld::addReferenceAction(QMenu* menu, duint 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 DisassemblerGraphViewOld::setupContextMenu() +{ + mMenuBuilder = new MenuBuilder(this, [](QMenu*) + { + return DbgIsDebugging(); + }); + + mMenuBuilder->addAction(makeShortcutAction(DIcon(QString("processor%1.png").arg(ArchValue("32", "64"))), tr("Follow in &Disassembler"), SLOT(followDisassemblerSlot()), "ActionGraphFollowDisassembler"), [this](QMenu*) + { + return this->cur_instr != 0; + }); + mMenuBuilder->addSeparator(); + + auto breakpointMenu = new BreakpointMenu(this, getActionHelperFuncs(), [this]() + { + return cur_instr; + }); + breakpointMenu->build(mMenuBuilder); + mMenuBuilder->addAction(makeShortcutAction(DIcon("comment.png"), tr("&Comment"), SLOT(setCommentSlot()), "ActionSetComment")); + mMenuBuilder->addAction(makeShortcutAction(DIcon("label.png"), tr("&Label"), SLOT(setLabelSlot()), "ActionSetLabel")); + MenuBuilder* gotoMenu = new MenuBuilder(this); + gotoMenu->addAction(makeShortcutAction(DIcon("geolocation-goto.png"), tr("Expression"), SLOT(gotoExpressionSlot()), "ActionGotoExpression")); + gotoMenu->addAction(makeShortcutAction(DIcon("cbp.png"), tr("Origin"), SLOT(gotoOriginSlot()), "ActionGotoOrigin")); + gotoMenu->addAction(makeShortcutAction(DIcon("previous.png"), tr("Previous"), SLOT(gotoPreviousSlot()), "ActionGotoPrevious"), [this](QMenu*) + { + return mHistory.historyHasPrev(); + }); + gotoMenu->addAction(makeShortcutAction(DIcon("next.png"), tr("Next"), SLOT(gotoNextSlot()), "ActionGotoNext"), [this](QMenu*) + { + return mHistory.historyHasNext(); + }); + MenuBuilder* childrenAndParentMenu = new MenuBuilder(this, [this](QMenu * menu) + { + duint cursorpos = get_cursor_pos(); + const DisassemblerBlock* currentBlock = nullptr; + const Instr* currentInstruction = nullptr; + for(const auto & i : blocks) + { + if(i.second.block.entry > cursorpos) + continue; + for(const Instr & inst : i.second.block.instrs) + { + if(inst.addr <= cursorpos && inst.addr + inst.opcode.size() > cursorpos) + { + currentBlock = &i.second; + currentInstruction = &inst; + break; + } + } + if(currentInstruction) + break; + } + if(currentInstruction) + { + for(const duint & i : currentBlock->incoming) // This list is incomplete + addReferenceAction(menu, i); + if(!currentBlock->block.terminal) + { + menu->addSeparator(); + for(const duint & i : currentBlock->block.exits) + addReferenceAction(menu, i); + } + //to do: follow a constant + return true; + } + return false; + }); + gotoMenu->addSeparator(); + gotoMenu->addBuilder(childrenAndParentMenu); + mMenuBuilder->addMenu(makeMenu(DIcon("goto.png"), tr("Go to")), gotoMenu); + mMenuBuilder->addAction(makeShortcutAction(DIcon("xrefs.png"), tr("Xrefs..."), SLOT(xrefSlot()), "ActionXrefs")); + mMenuBuilder->addAction(makeShortcutAction(DIcon("snowman.png"), tr("Decompile"), SLOT(decompileSlot()), "ActionGraphDecompile")); + mMenuBuilder->addSeparator(); + mMenuBuilder->addAction(mToggleOverview = makeShortcutAction(DIcon("graph.png"), tr("&Overview"), SLOT(toggleOverviewSlot()), "ActionGraphToggleOverview")); + mToggleOverview->setCheckable(true); + mMenuBuilder->addAction(mToggleSummary = makeShortcutAction(DIcon("summary.png"), tr("S&ummary"), SLOT(toggleSummarySlot()), "ActionGraphToggleSummary")); + mToggleSummary->setCheckable(true); + mMenuBuilder->addAction(mToggleSyncOrigin = makeShortcutAction(DIcon("lock.png"), tr("&Sync with origin"), SLOT(toggleSyncOriginSlot()), "ActionGraphSyncOrigin")); + mMenuBuilder->addAction(makeShortcutAction(DIcon("sync.png"), tr("&Refresh"), SLOT(refreshSlot()), "ActionRefresh")); + mMenuBuilder->addAction(makeShortcutAction(DIcon("image.png"), tr("&Save as image"), SLOT(saveImageSlot()), "ActionGraphSaveImage")); + + MenuBuilder* layoutMenu = new MenuBuilder(this); + QActionGroup* layoutGroup = new QActionGroup(this); + layoutGroup->addAction(makeAction(DIcon("narrow.png"), tr("Narrow"), [this]() { setGraphLayout(LayoutType::Narrow); })); + QAction* mediumLayout = + layoutGroup->addAction(makeAction(DIcon("medium.png"), tr("Medium"), [this]() { setGraphLayout(LayoutType::Medium); })); + layoutGroup->addAction(makeAction(DIcon("wide.png"), tr("Wide"), [this]() { setGraphLayout(LayoutType::Wide); })); + for(QAction* layoutAction : layoutGroup->actions()) + { + layoutAction->setCheckable(true); + layoutMenu->addAction(layoutAction); + } + mediumLayout->setChecked(true); + mMenuBuilder->addMenu(makeMenu(DIcon("layout.png"), tr("Layout")), layoutMenu); + + mMenuBuilder->loadFromConfig(); +} + +void DisassemblerGraphViewOld::keyPressEvent(QKeyEvent* 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()); + else if(key == Qt::Key_Return || key == Qt::Key_Enter) + { + //Add address to history + if(!mHistoryLock) + mHistory.addVaToHistory(cur_instr); + DbgCmdExec(QString("graph dis.branchdest(%1), silent").arg(ToPtrString(cur_instr)).toUtf8().constData()); + } +} + +void DisassemblerGraphViewOld::followDisassemblerSlot() +{ + DbgCmdExec(QString("disasm %1").arg(ToPtrString(this->cur_instr)).toUtf8().constData()); +} + +void DisassemblerGraphViewOld::colorsUpdatedSlot() +{ + 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(); + loadCurrentGraph(); +} + +void DisassemblerGraphViewOld::fontsUpdatedSlot() +{ + fontChanged(); +} + +void DisassemblerGraphViewOld::shortcutsUpdatedSlot() +{ + updateShortcuts(); +} + +void DisassemblerGraphViewOld::toggleOverviewSlot() +{ + drawOverview = !drawOverview; + if(onlySummary) + { + onlySummary = false; + mToggleSummary->setChecked(false); + loadCurrentGraph(); + } + else + this->viewport()->update(); +} + +void DisassemblerGraphViewOld::toggleSummarySlot() +{ + drawOverview = false; + onlySummary = !onlySummary; + loadCurrentGraph(); +} + +void DisassemblerGraphViewOld::selectionGetSlot(SELECTIONDATA* selection) +{ + selection->start = selection->end = cur_instr; + Bridge::getBridge()->setResult(1); +} + +void DisassemblerGraphViewOld::disassembleAtSlot(dsint va, dsint cip) +{ + Q_UNUSED(va); + auto cipChanged = mCip != cip; + mCip = cip; + if(syncOrigin && cipChanged) + gotoOriginSlot(); + else + this->viewport()->update(); +} + +void DisassemblerGraphViewOld::gotoExpressionSlot() +{ + if(!DbgIsDebugging()) + return; + if(!mGoto) + mGoto = new GotoDialog(this); + mGoto->setInitialExpression(ToPtrString(this->cur_instr)); + if(mGoto->exec() == QDialog::Accepted) + { + duint value = DbgValFromString(mGoto->expressionText.toUtf8().constData()); + DbgCmdExec(QString().sprintf("graph %p, silent", value).toUtf8().constData()); + } +} + +void DisassemblerGraphViewOld::gotoOriginSlot() +{ + DbgCmdExec("graph cip, silent"); +} + +void DisassemblerGraphViewOld::gotoPreviousSlot() +{ + if(mHistory.historyHasPrev()) + { + mHistoryLock = true; + DbgCmdExecDirect(QString("graph %1, silent").arg(ToPtrString(mHistory.historyPrev())).toUtf8().constData()); + mHistoryLock = false; + } +} + +void DisassemblerGraphViewOld::gotoNextSlot() +{ + if(mHistory.historyHasNext()) + { + mHistoryLock = true; + DbgCmdExecDirect(QString("graph %1, silent").arg(ToPtrString(mHistory.historyNext())).toUtf8().constData()); + mHistoryLock = false; + } +} + +void DisassemblerGraphViewOld::toggleSyncOriginSlot() +{ + syncOrigin = !syncOrigin; + mToggleSyncOrigin->setCheckable(true); + mToggleSyncOrigin->setChecked(syncOrigin); + if(syncOrigin) + gotoOriginSlot(); +} + +void DisassemblerGraphViewOld::refreshSlot() +{ + DbgCmdExec(QString("graph %1, force").arg(ToPtrString(this->cur_instr)).toUtf8().constData()); +} + +void DisassemblerGraphViewOld::saveImageSlot() +{ + saveGraph = true; + this->viewport()->update(); +} + +void DisassemblerGraphViewOld::setCommentSlot() +{ + 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(); +} + +void DisassemblerGraphViewOld::setLabelSlot() +{ + 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) + { + 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; + } + if(!DbgSetLabelAt(wVA, utf8data.constData())) + SimpleErrorBox(this, tr("Error!"), tr("DbgSetLabelAt failed!")); + + this->refreshSlot(); +} + +void DisassemblerGraphViewOld::xrefSlot() +{ + + if(!DbgIsDebugging()) + return; + duint wVA = this->get_cursor_pos(); + if(!DbgMemIsValidReadPtr(wVA)) + return; + XREF_INFO mXrefInfo; + DbgXrefGet(wVA, &mXrefInfo); + if(!mXrefInfo.refcount) + return; + BridgeFree(mXrefInfo.references); + if(!mXrefDlg) + mXrefDlg = new XrefBrowseDialog(this); + mXrefDlg->setup(wVA, "graph"); + mXrefDlg->showNormal(); +} + +void DisassemblerGraphViewOld::decompileSlot() +{ + std::vector ranges; + ranges.reserve(currentGraph.nodes.size()); + + if(!DbgIsDebugging()) + return; + if(currentGraph.nodes.empty()) + return; + SnowmanRange r; + for(const auto & nodeIt : currentGraph.nodes) + { + 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); + } + std::sort(ranges.begin(), ranges.end(), [](const SnowmanRange & a, const SnowmanRange & b) + { + return a.start > b.start; + }); + emit displaySnowmanWidget(); + DecompileRanges(Bridge::getBridge()->snowmanView, ranges.data(), ranges.size()); +} + +void DisassemblerGraphViewOld::followActionSlot() +{ + QAction* action = qobject_cast(sender()); + if(action) + { + QString data = action->data().toString(); + DbgCmdExecDirect(QString("graph %1, silent").arg(data).toUtf8().constData()); + } +} diff --git a/src/gui/Src/Gui/DisassemblerGraphViewOld.h b/src/gui/Src/Gui/DisassemblerGraphViewOld.h new file mode 100644 index 00000000..da3a9622 --- /dev/null +++ b/src/gui/Src/Gui/DisassemblerGraphViewOld.h @@ -0,0 +1,353 @@ +#ifndef DISASSEMBLERGRAPHVIEWOLD_H +#define DISASSEMBLERGRAPHVIEWOLD_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Bridge.h" +#include "RichTextPainter.h" +#include "QBeaEngine.h" +#include "ActionHelpers.h" +#include "VaHistory.h" + +class MenuBuilder; +class CachedFontMetrics; +class GotoDialog; +class XrefBrowseDialog; + +class DisassemblerGraphViewOld : public QAbstractScrollArea, public ActionHelper +{ + 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 std::move(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; + std::vector blocks; + }; + + struct Analysis + { + duint entry = 0; + std::unordered_map functions; + bool ready = false; + 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, + }; + + DisassemblerGraphViewOld(QWidget* parent = nullptr); + ~DisassemblerGraphViewOld(); + 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(); + +public slots: + 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 xrefSlot(); + void decompileSlot(); + +private: + QString status; + Analysis analysis; + duint function; + QTimer* updateTimer; + 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; + 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; + XrefBrowseDialog* mXrefDlg; + + void addReferenceAction(QMenu* menu, duint addr); +}; + +#endif // DISASSEMBLERGRAPHVIEWOLD_H diff --git a/src/gui/Src/Gui/GraphView.cpp b/src/gui/Src/Gui/GraphView.cpp new file mode 100644 index 00000000..be573630 --- /dev/null +++ b/src/gui/Src/Gui/GraphView.cpp @@ -0,0 +1,992 @@ +#include "GraphView.h" + +#include +#include +#include +#include +#include + +GraphView::GraphView(QWidget* parent) + : QAbstractScrollArea(parent) +{ + setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + horizontalScrollBar()->setSingleStep(charWidth); + verticalScrollBar()->setSingleStep(charWidth); + QSize areaSize = viewport()->size(); + 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::blockDoubleClicked(GraphView::GraphBlock & block, QMouseEvent* event, QPoint pos) +{ + Q_UNUSED(block); + Q_UNUSED(event); + Q_UNUSED(pos); + qWarning() << "Block double clicked not overridden!"; +} + +void GraphView::blockHelpEvent(GraphView::GraphBlock & block, QHelpEvent* event, QPoint pos) +{ + Q_UNUSED(block); + Q_UNUSED(event); + Q_UNUSED(pos); +} + +bool GraphView::helpEvent(QHelpEvent* event) +{ + int x = ((event->pos().x() - unscrolled_render_offset_x) / current_scale) + horizontalScrollBar()->value(); + int y = ((event->pos().y() - unscrolled_render_offset_y) / current_scale) + verticalScrollBar()->value(); + + 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); + blockHelpEvent(block, event, pos); + return true; + } + } + + return false; +} + +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, width - (new_width / current_scale)); + verticalScrollBar()->setPageStep(new_height); + verticalScrollBar()->setRange(0, height - (new_height / current_scale)); + horizontalScrollBar()->setValue((int)((double)horizontalScrollBar()->maximum() * hfactor)); + verticalScrollBar()->setValue((int)((double)verticalScrollBar()->maximum() * vfactor)); +} + +bool GraphView::event(QEvent* event) +{ + if(event->type() == QEvent::ToolTip) + { + if(helpEvent(static_cast(event))) + { + return true; + } + } + + return QAbstractScrollArea::event(event); +} + +// This calculates the full graph starting at block entry. +void GraphView::computeGraph(ut64 entry) +{ + QSize areaSize = viewport()->size(); + + // Populate incoming lists + for(auto & blockIt : blocks) + { + GraphBlock & block = blockIt.second; + for(auto & edge : block.exits) + { + 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 = 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(!blocks.count(edge)) + { + continue; + } + + // If this node has no other incoming edges, add it to the graph layout + if(blocks[edge].incoming.size() == 1) + { + removeFromVec(blocks[edge].incoming, block.entry); + block.new_exits.push_back(edge); + queue.push(blocks[edge].entry); + visited.insert(edge); + changed = true; + } + else + { + // Remove from incoming edges + removeFromVec(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 : 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(!blocks.count(edge)) + { + continue; + } + // find best edge + if((best == 0) || ((int)blocks[edge].incoming.size() < best_edges) || ( + ((int)blocks[edge].incoming.size() == best_edges) && (edge < best))) + { + best = edge; + best_edges = blocks[edge].incoming.size(); + best_parent = block.entry; + } + } + } + if(best != 0) + { + GraphBlock & best_parentb = blocks[best_parent]; + removeFromVec(blocks[best].incoming, best_parentb.entry); + best_parentb.new_exits.push_back(best); + visited.insert(best); + queue.push(best); + changed = true; + } + } + + computeGraphLayout(blocks[entry]); + + // Prepare edge routing + GraphBlock & entryb = 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 : 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 = blocks[edge]; + start.edges.push_back(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 : 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(col_edge_x, entryb.col_count + 1, 0); + initVec(row_edge_y, entryb.row_count + 1, 0); + int x = block_horizontal_margin * 2; + for(int i = 0; i < entryb.col_count; i++) + { + col_edge_x[i] = x; + x += block_horizontal_margin * col_edge_count[i]; + col_x[i] = x; + x += col_width[i]; + } + int y = block_vertical_margin * 2; + for(int i = 0; i < entryb.row_count; i++) + { + row_edge_y[i] = y; + // TODO: The 1 when row_edge_count is 0 is not needed on the original.. not sure why it's required for us + if(!row_edge_count[i]) + { + row_edge_count[i] = 1; + } + y += block_vertical_margin * row_edge_count[i]; + row_y[i] = y; + y += row_height[i]; + } + col_edge_x[entryb.col_count] = x; + row_edge_y[entryb.row_count] = y; + width = x + (block_horizontal_margin * 2) + (block_horizontal_margin * col_edge_count[entryb.col_count]); + height = y + (block_vertical_margin * 2) + (block_vertical_margin * row_edge_count[entryb.row_count]); + + //Compute node positions + for(auto & blockIt : blocks) + { + GraphBlock & block = blockIt.second; + block.x = int( + (col_x[block.col] + col_width[block.col] + ((block_horizontal_margin / 2) * 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] + block_horizontal_margin * col_edge_count[ + block.col + 1])) + { + block.x = int((col_x[block.col] + col_width[block.col] + col_width[block.col + 1] + block_horizontal_margin * col_edge_count[ + block.col + 1]) - block.width); + } + block.y = row_y[block.row]; + } + + // Precompute coordinates for edges + for(auto & blockIt : 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; + // This is the start point of the edge. + auto first_pt = QPoint(col_edge_x[start_col] + (block_horizontal_margin * last_index) + (block_horizontal_margin / 2), + 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(), row_edge_y[end_row] + (block_vertical_margin * last_index) + (block_vertical_margin / 2)); + else + new_pt = QPoint(col_edge_x[end_col] + (block_horizontal_margin * last_index) + (block_horizontal_margin / 2), 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; + } + if(ec.end_arrow) + { + 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; + } + } + } + + ready = true; + + viewport()->update(); + areaSize = viewport()->size(); + adjustSize(areaSize.width(), areaSize.height()); +} + +void GraphView::paintEvent(QPaintEvent* event) +{ + Q_UNUSED(event); + QPainter p(viewport()); + int render_offset_x = -horizontalScrollBar()->value() * current_scale; + int render_offset_y = -verticalScrollBar()->value() * current_scale; + int render_width = viewport()->size().width() / current_scale; + int render_height = viewport()->size().height() / current_scale; + + // Do we have scrollbars? + bool hscrollbar = horizontalScrollBar()->pageStep() < width; + bool vscrollbar = verticalScrollBar()->pageStep() < height; + + // Draw background + QRect viewportRect(viewport()->rect().topLeft(), viewport()->rect().bottomRight() - QPoint(1, 1)); + p.setBrush(backgroundColor); + p.drawRect(viewportRect); + p.setBrush(Qt::black); + + unscrolled_render_offset_x = 0; + unscrolled_render_offset_y = 0; + + // We do not have a scrollbar on this axis, so we center the view + if(!hscrollbar) + { + unscrolled_render_offset_x = (viewport()->size().width() - (width * current_scale)) / 2; + render_offset_x += unscrolled_render_offset_x; + } + if(!vscrollbar) + { + unscrolled_render_offset_y = (viewport()->size().height() - (height * current_scale)) / 2; + render_offset_y += unscrolled_render_offset_y; + } + + p.translate(render_offset_x, render_offset_y); + p.scale(current_scale, current_scale); + + + // Draw blocks + for(auto & blockIt : 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 = blocks[edge]; + computeGraphLayout(edgeb); + row_count = std::max(edgeb.row_count + 1, row_count); + childColumn = edgeb.col; + } + + if(layoutType != LayoutType::Wide && block.new_exits.size() == 2) + { + GraphBlock & left = blocks[block.new_exits[0]]; + GraphBlock & right = blocks[block.new_exits[1]]; + if(left.new_exits.size() == 0) + { + left.col = right.col - 2; + int add = left.col < 0 ? - left.col : 0; + adjustGraphLayout(right, add, 1); + adjustGraphLayout(left, add, 1); + col = right.col_count + add; + } + else if(right.new_exits.size() == 0) + { + adjustGraphLayout(left, 0, 1); + adjustGraphLayout(right, left.col + 2, 1); + col = std::max(left.col_count, right.col + 2); + } + else + { + adjustGraphLayout(left, 0, 1); + 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) + { + adjustGraphLayout(blocks[edge], col, 1); + col += 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(!isEdgeMarked(vert_edges, start.row + 1, start.col + 1, i)) + break; + i += 1; + } + 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 = 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)) + markEdge(vert_edges, start.row + 1, start.col + 1, i, false); + int index = 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 = 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 = 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++) + 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++) + 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(horizontalScrollBar()->maximum(), target_x); + target_y = std::min(verticalScrollBar()->maximum(), target_y); + if(animated) + { + QPropertyAnimation* animation_x = new QPropertyAnimation(horizontalScrollBar(), "value"); + animation_x->setDuration(500); + animation_x->setStartValue(horizontalScrollBar()->value()); + animation_x->setEndValue(target_x); + animation_x->setEasingCurve(QEasingCurve::InOutQuad); + animation_x->start(); + QPropertyAnimation* animation_y = new QPropertyAnimation(verticalScrollBar(), "value"); + animation_y->setDuration(500); + animation_y->setStartValue(verticalScrollBar()->value()); + animation_y->setEndValue(target_y); + animation_y->setEasingCurve(QEasingCurve::InOutQuad); + animation_y->start(); + } + else + { + horizontalScrollBar()->setValue(target_x); + verticalScrollBar()->setValue(target_y); + } + + blockTransitionedTo(block); + + viewport()->update(); +} + +void GraphView::adjustGraphLayout(GraphBlock & block, int col, int row) +{ + block.col += col; + block.row += row; + for(ut64 edge : block.new_exits) + { + adjustGraphLayout(blocks[edge], col, row); + } +} + +void GraphView::addBlock(GraphView::GraphBlock block) +{ + blocks[block.entry] = block; +} + + +void GraphView::setEntry(ut64 e) +{ + 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 : (3 * 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() - unscrolled_render_offset_x) / current_scale) + horizontalScrollBar()->value(); + int y = ((event->pos().y() - unscrolled_render_offset_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 : 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 + scroll_base_x = event->x(); + scroll_base_y = event->y(); + scroll_mode = true; + setCursor(Qt::ClosedHandCursor); + viewport()->grabMouse(); + } + +} + +void GraphView::mouseMoveEvent(QMouseEvent* event) +{ + if(scroll_mode) + { + int x_delta = scroll_base_x - event->x(); + int y_delta = scroll_base_y - event->y(); + scroll_base_x = event->x(); + scroll_base_y = event->y(); + horizontalScrollBar()->setValue(horizontalScrollBar()->value() + x_delta); + verticalScrollBar()->setValue(verticalScrollBar()->value() + y_delta); + } +} + +void GraphView::mouseDoubleClickEvent(QMouseEvent* event) +{ + int x = ((event->pos().x() - unscrolled_render_offset_x) / current_scale) + horizontalScrollBar()->value(); + int y = ((event->pos().y() - unscrolled_render_offset_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); + blockDoubleClicked(block, event, pos); + return; + } + } +} + +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(scroll_mode) + { + scroll_mode = false; + setCursor(Qt::ArrowCursor); + viewport()->releaseMouse(); + } +} diff --git a/src/gui/Src/Gui/GraphView.h b/src/gui/Src/Gui/GraphView.h new file mode 100644 index 00000000..8f8cfaf9 --- /dev/null +++ b/src/gui/Src/Gui/GraphView.h @@ -0,0 +1,182 @@ +#ifndef GRAPHVIEW_H +#define GRAPHVIEW_H + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +//#include "cutter.h" + +using ut64 = size_t; + +class GraphView : public QAbstractScrollArea +{ + Q_OBJECT + + enum class LayoutType + { + Medium, + Wide, + 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 = 0; + // Number of columns in block + int col_count = 0; + // Column in which the block is + int col = 0; + // Row in which the block is + int row = 0; + + // 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 = 20; + int block_horizontal_margin = 10; + + // Padding inside the block + int block_padding = 16; + + // Zoom data + double current_scale = 1.0; + + int unscrolled_render_offset_x = 0; + int unscrolled_render_offset_y = 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 blockDoubleClicked(GraphView::GraphBlock & block, QMouseEvent* event, QPoint pos); + virtual void blockHelpEvent(GraphView::GraphBlock & block, QHelpEvent* event, QPoint pos); + virtual bool helpEvent(QHelpEvent* event); + virtual void blockTransitionedTo(GraphView::GraphBlock* to); + virtual EdgeConfiguration edgeConfiguration(GraphView::GraphBlock & from, GraphView::GraphBlock* to); + + void adjustSize(int new_width, int new_height); + + bool event(QEvent* event); +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; + void mouseDoubleClickEvent(QMouseEvent* event) override; + +}; + +#endif // GRAPHVIEW_H diff --git a/src/gui/Src/Gui/MainWindow.cpp b/src/gui/Src/Gui/MainWindow.cpp index 7c3823bd..eb81e9b3 100644 --- a/src/gui/Src/Gui/MainWindow.cpp +++ b/src/gui/Src/Gui/MainWindow.cpp @@ -17,6 +17,7 @@ #include "FavouriteTools.h" #include "CPUDisassembly.h" #include "CloseDialog.h" +#include #include "CommandLineEdit.h" #include "TabWidget.h" #include "CPUWidget.h" @@ -40,7 +41,7 @@ #include "TimeWastedCounter.h" #include "NotesManager.h" #include "SettingsDialog.h" -#include "DisassemblerGraphView.h" +#include "DisassemblerGraphViewOld.h" #include "CPUMultiDump.h" #include "CPUStack.h" #include "GotoDialog.h" @@ -201,7 +202,7 @@ MainWindow::MainWindow(QWidget* parent) mHandlesView->setWindowIcon(DIcon("handles.png")); // Graph view - mGraphView = new DisassemblerGraphView(this); + mGraphView = new DisassemblerGraphViewOld(this); mGraphView->setWindowTitle(tr("Graph")); mGraphView->setWindowIcon(DIcon("graph.png")); @@ -220,13 +221,18 @@ MainWindow::MainWindow(QWidget* parent) mWidgetList.push_back(WidgetInfo(mNotesManager, "NotesTab")); mWidgetList.push_back(WidgetInfo(mBreakpointsView, "BreakpointsTab")); mWidgetList.push_back(WidgetInfo(mMemMapView, "MemoryMapTab")); - mWidgetList.push_back(WidgetInfo(mCallStackView, "CallStackTab")); + //mWidgetList.push_back(WidgetInfo(mCallStackView, "CallStackTab")); mWidgetList.push_back(WidgetInfo(mSEHChainView, "SEHTab")); mWidgetList.push_back(WidgetInfo(mScriptView, "ScriptTab")); mWidgetList.push_back(WidgetInfo(mSymbolView, "SymbolsTab")); mWidgetList.push_back(WidgetInfo(mSourceViewManager, "SourceTab")); mWidgetList.push_back(WidgetInfo(mReferenceManager, "ReferencesTab")); - mWidgetList.push_back(WidgetInfo(mThreadView, "ThreadsTab")); + QVBoxLayout* x = new QVBoxLayout(); + x->addWidget(mThreadView); + x->addWidget(mCallStackView); + QWidget* w = new QWidget(); + w->setLayout(x); + mWidgetList.push_back(WidgetInfo(w, "ThreadsTab")); mWidgetList.push_back(WidgetInfo(mSnowmanView, "SnowmanTab")); mWidgetList.push_back(WidgetInfo(mHandlesView, "HandlesTab")); mWidgetList.push_back(WidgetInfo(mTraceBrowser, "TraceTab")); diff --git a/src/gui/Src/Gui/MainWindow.h b/src/gui/Src/Gui/MainWindow.h index 1836fdec..29f1e959 100644 --- a/src/gui/Src/Gui/MainWindow.h +++ b/src/gui/Src/Gui/MainWindow.h @@ -29,7 +29,7 @@ class MainWindowCloseThread; class TimeWastedCounter; class NotesManager; class SettingsDialog; -class DisassemblerGraphView; +class DisassemblerGraphViewOld; class SimpleTraceDialog; class MRUList; class UpdateChecker; @@ -179,7 +179,7 @@ private: QWidget* mSnowmanView; HandlesView* mHandlesView; NotesManager* mNotesManager; - DisassemblerGraphView* mGraphView; + DisassemblerGraphViewOld* mGraphView; TraceBrowser* mTraceBrowser; SimpleTraceDialog* mSimpleTraceDialog; UpdateChecker* mUpdateChecker; diff --git a/src/gui/x64dbg.pro b/src/gui/x64dbg.pro index aa87449c..1fb9dad0 100644 --- a/src/gui/x64dbg.pro +++ b/src/gui/x64dbg.pro @@ -168,7 +168,6 @@ SOURCES += \ Src/Gui/WatchView.cpp \ Src/Gui/FavouriteTools.cpp \ Src/Gui/BrowseDialog.cpp \ - Src/Gui/DisassemblerGraphView.cpp \ Src/Gui/DisassemblyPopup.cpp \ Src/Gui/VirtualModDialog.cpp \ Src/BasicView/LabeledSplitter.cpp \ @@ -189,7 +188,10 @@ SOURCES += \ Src/Tracer/TraceBrowser.cpp \ Src/Tracer/TraceFileReader.cpp \ Src/Tracer/TraceFileSearch.cpp \ - Src/Gui/MultiItemsSelectWindow.cpp + Src/Gui/MultiItemsSelectWindow.cpp \ + Src/Gui/DisassemblerGraphViewOld.cpp \ + Src/Gui/GraphView.cpp \ + Src/Gui/DisassemblerGraphView.cpp HEADERS += \ @@ -290,7 +292,6 @@ HEADERS += \ Src/Gui/WatchView.h \ Src/Gui/FavouriteTools.h \ Src/Gui/BrowseDialog.h \ - Src/Gui/DisassemblerGraphView.h \ Src/Utils/ActionHelpers.h \ Src/Gui/DisassemblyPopup.h \ Src/Gui/VirtualModDialog.h \ @@ -313,7 +314,10 @@ HEADERS += \ Src/Tracer/TraceFileReader.h \ Src/Tracer/TraceFileReaderInternal.h \ Src/Tracer/TraceFileSearch.h \ - Src/Gui/MultiItemsSelectWindow.h + Src/Gui/MultiItemsSelectWindow.h \ + Src/Gui/DisassemblerGraphViewOld.h \ + Src/Gui/GraphView.h \ + Src/Gui/DisassemblerGraphView.h FORMS += \