/* x64dbg DisassemblerGraphView */ #include "DisassemblerGraphView.h" #include "menus/DisassemblyContextMenu.h" #include #include #include #include #include #include #include #include #include "dialogs/XrefsDialog.h" #include "utils/Configuration.h" #include "utils/Colors.h" #ifdef _WIN32 #undef min #undef max #endif DisassemblerGraphView::DisassemblerGraphView(QWidget *parent) : QAbstractScrollArea(parent), //currentGraph(duint(0)), //disasm(ConfigUint("Disassembler", "MaxModuleSize")), mFontMetrics(nullptr), syncOrigin(false), mCip(0), forceCenter(false), mHistoryLock(false), layoutType(LayoutType::Medium), mGoto(nullptr), mXrefDlg(nullptr) { this->status = "Loading..."; //Start disassembly view at the entry point of the binary this->function = 0; this->update_id = 0; this->ready = false; this->desired_pos = nullptr; this->highlight_token = nullptr; this->cur_instr = 0; this->scroll_base_x = 0; this->scroll_base_y = 0; this->scroll_mode = false; this->drawOverview = false; this->onlySummary = false; this->blocks.clear(); this->saveGraph = false; //Create timer to automatically refresh view when it needs to be updated //this->updateTimer = new QTimer(); //this->updateTimer->setInterval(1000); // TODO Probably too slow //this->updateTimer->setSingleShot(false); //connect(this->updateTimer, SIGNAL(timeout()), this, SLOT(updateTimerEvent())); //this->updateTimer->start(); // Remove above comments? // Draw the first graph after 1s this->updateTimer = new QTimer(); this->updateTimer->setSingleShot(true); connect(this->updateTimer, SIGNAL(timeout()), this, SLOT(updateTimerEvent())); this->updateTimer->start(1000); 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(Core(), SIGNAL(seekChanged(RVA)), this, SLOT(on_seekChanged(RVA))); //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(); } DisassemblerGraphView::~DisassemblerGraphView() { delete this->highlight_token; } void DisassemblerGraphView::initFont() { setFont(Config()->getFont()); QFontMetricsF metrics(this->font()); this->baseline = int(metrics.ascent()); this->charWidth = metrics.width('X'); this->charHeight = metrics.height(); this->charOffset = 0; if(mFontMetrics) delete mFontMetrics; mFontMetrics = new CachedFontMetrics(this, font()); } void DisassemblerGraphView::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 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) { Q_UNUSED(addr); if(!this->navigate(addr)) { //TODO: show in hex editor? } } std::tuple DisassemblerGraphView::get_selection_range() { return std::make_tuple(get_cursor_pos(), get_cursor_pos()); } void DisassemblerGraphView::set_selection_range(std::tuple range) { this->set_cursor_pos(std::get<0>(range)); } void DisassemblerGraphView::copy_address() { QClipboard* clipboard = QApplication::clipboard(); clipboard->clear(); QMimeData mime; mime.setText(QString().sprintf("0x%llx", this->get_cursor_pos())); clipboard->setMimeData(&mime); } void DisassemblerGraphView::paintNormal(QPainter & p, QRect & viewportRect, int xofs, int yofs) { //Translate the painter //auto dbgfunctions = DbgFunctions(); QPoint translation(this->renderXOfs - xofs, this->renderYOfs - yofs); p.translate(translation); viewportRect.translate(-translation.x(), -translation.y()); //Render each node for(auto & blockIt : this->blocks) { DisassemblerBlock & block = blockIt.second; bool blockSelected = false; for(const Instr & instr : block.block.instrs) { 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) { auto selected = instr.addr == this->cur_instr; //auto traceCount = dbgfunctions->GetTraceRecordHitCount(instr.addr); auto traceCount = 0; if(selected && traceCount) { p.fillRect(QRect(block.x + this->charWidth + 3, y, block.width - (10 + 2 * this->charWidth), int(instr.text.lines.size()) * this->charHeight), disassemblyTracedSelectionColor); } else if(selected) { p.fillRect(QRect(block.x + this->charWidth + 3, y, block.width - (10 + 2 * this->charWidth), int(instr.text.lines.size()) * this->charHeight), disassemblySelectionColor); } else if(traceCount) { // Color depending on how often a sequence of code is executed int exponent = 1; while(traceCount >>= 1) //log2(traceCount) exponent++; int colorDiff = (exponent * exponent) / 2; // If the user has a light trace background color, substract if(disassemblyTracedColor.blue() > 160) colorDiff *= -1; p.fillRect(QRect(block.x + this->charWidth + 3, y, block.width - (10 + 2 * this->charWidth), int(instr.text.lines.size()) * this->charHeight), QColor(disassemblyTracedColor.red(), disassemblyTracedColor.green(), std::max(0, std::min(256, disassemblyTracedColor.blue() + colorDiff)))); } y += int(instr.text.lines.size()) * this->charHeight; } } //Render node text auto x = block.x + (2 * this->charWidth); auto y = block.y + (2 * this->charWidth); for(auto & line : block.block.header_text.lines) { 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) { int rectSize = qRound(this->charWidth); if(rectSize % 2) rectSize++; // Assume charWidth <= charHeight QRectF bpRect(x - rectSize / 3.0, y + (this->charHeight - rectSize) / 2.0, rectSize, rectSize); //bool isbp = DbgGetBpxTypeAt(instr.addr) != bp_none; //bool isbpdisabled = DbgIsBpDisabled(instr.addr); bool isbp = false; bool isbpdisabled = true; bool iscip = instr.addr == mCip; if(isbp || isbpdisabled) { if(iscip) { // Left half is cip bpRect.setWidth(bpRect.width() / 2); p.fillRect(bpRect, mCipColor); // Right half is breakpoint bpRect.translate(bpRect.width(), 0); } p.fillRect(bpRect, isbp ? mBreakpointColor : mDisabledBreakpointColor); } else if(iscip) p.fillRect(bpRect, mCipColor); RichTextPainter::paintRichText(&p, x + this->charWidth, y, block.width - this->charWidth, this->charHeight, 0, line, mFontMetrics); y += this->charHeight; } } } // Render edges for(DisassemblerEdge & edge : block.edges) { QPen pen(edge.color); if(blockSelected) pen.setStyle(Qt::DashLine); p.setPen(pen); p.setBrush(edge.color); p.drawPolyline(edge.polyline); pen.setStyle(Qt::SolidLine); p.setPen(pen); p.drawConvexPolygon(edge.arrow); } } } void DisassemblerGraphView::paintOverview(QPainter & p, QRect & viewportRect, int xofs, int yofs) { // Scale and translate painter //auto dbgfunctions = DbgFunctions(); qreal sx = qreal(viewportRect.width()) / qreal(this->renderWidth); qreal sy = qreal(viewportRect.height()) / qreal(this->renderHeight); qreal s = qMin(sx, sy); this->overviewScale = s; if(sx < sy) { this->overviewXOfs = this->renderXOfs * s; this->overviewYOfs = this->renderYOfs * s + (qreal(this->renderHeight) * sy - qreal(this->renderHeight) * s) / 2; } else if(sy < sx) { this->overviewXOfs = this->renderXOfs * s + (qreal(this->renderWidth) * sx - qreal(this->renderWidth) * s) / 2; this->overviewYOfs = this->renderYOfs * s; } else { this->overviewXOfs = this->renderXOfs; this->overviewYOfs = this->renderYOfs; } p.translate(this->overviewXOfs, this->overviewYOfs); p.scale(s, s); // Scaled pen QPen pen; qreal penWidth = 1.0 / s; pen.setWidthF(penWidth); //Render each node duint cipBlock = 0; auto found = currentBlockMap.find(mCip); if(found != currentBlockMap.end()) cipBlock = found->second; for(auto & blockIt : this->blocks) { DisassemblerBlock & block = blockIt.second; // Render edges for(DisassemblerEdge & edge : block.edges) { pen.setColor(edge.color); p.setPen(pen); p.setBrush(edge.color); p.drawPolyline(edge.polyline); p.drawConvexPolygon(edge.arrow); } //Get block metadata //auto traceCount = dbgfunctions->GetTraceRecordHitCount(block.block.entry); auto traceCount = 0; auto isCip = block.block.entry == cipBlock; //Render shadow p.setPen(QColor(0, 0, 0, 0)); if((isCip || traceCount) && block.block.terminal) p.setBrush(retShadowColor); else if((isCip || traceCount) && block.block.indirectcall) p.setBrush(indirectcallShadowColor); else if(isCip) p.setBrush(QColor(0, 0, 0, 0)); else p.setBrush(QColor(0, 0, 0, 128)); p.drawRect(block.x + this->charWidth + 4, block.y + this->charWidth + 4, block.width - (4 + 2 * this->charWidth), block.height - (4 + 2 * this->charWidth)); //Render node background pen.setColor(graphNodeColor); p.setPen(pen); if(isCip) p.setBrush(mCipColor); else if(traceCount) { // Color depending on how often a sequence of code is executed int exponent = 1; while(traceCount >>= 1) //log2(traceCount) exponent++; int colorDiff = (exponent * exponent) / 2; // If the user has a light trace background color, substract if(disassemblyTracedColor.blue() > 160) colorDiff *= -1; p.setBrush(QColor(disassemblyTracedColor.red(), disassemblyTracedColor.green(), std::max(0, std::min(256, disassemblyTracedColor.blue() + colorDiff)))); } else if(block.block.terminal) p.setBrush(retShadowColor); else if(block.block.indirectcall) p.setBrush(indirectcallShadowColor); else p.setBrush(disassemblyBackgroundColor); p.drawRect(block.x + this->charWidth, block.y + this->charWidth, block.width - (4 + 2 * this->charWidth), block.height - (4 + 2 * this->charWidth)); } // 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) { Q_UNUSED(event); /* TODO //Convert coordinates to system used in blocks int xofs = this->horizontalScrollBar()->value(); int yofs = this->verticalScrollBar()->value(); int x = event->x() + xofs - this->renderXOfs; int y = event->y() + yofs - this->renderYOfs; //Check each block for hits for(auto & blockIt : this->blocks) { DisassemblerBlock & block = blockIt.second; //Compute coordinate relative to text area in block int blockx = x - (block.x + (2 * this->charWidth)); int blocky = y - (block.y + (2 * this->charWidth)); //Check to see if click is within bounds of block if((blockx < 0) || (blockx > (block.width - 4 * this->charWidth))) continue; if((blocky < 0) || (blocky > (block.height - 4 * this->charWidth))) continue; //Compute row and column within text int col = int(blockx / this->charWidth); int row = int(blocky / this->charHeight); //Check tokens to see if one was clicked int cur_row = 0; for(auto & line : block.block.header_text.tokens) { if(cur_row == row) { for(Token & token : line) { if((col >= token.start) && (col < (token.start + token.length))) { //Clicked on a token tokenOut = token; return true; } } } cur_row += 1; } for(Instr & instr : block.block.instrs) { for(auto & line : instr.text.tokens) { if(cur_row == row) { for(Token & token : line) { if((col >= token.start) && (col < (token.start + token.length))) { //Clicked on a token tokenOut = token; return true; } } } cur_row += 1; } } }*/ return false; } bool DisassemblerGraphView::find_instr(duint addr, Instr & instrOut) { for(auto & blockIt : this->blocks) for(Instr & instr : blockIt.second.block.instrs) if(instr.addr == addr) { instrOut = instr; return true; } return false; } void DisassemblerGraphView::mousePressEvent(QMouseEvent* event) { //if(!DbgIsDebugging()) // return; if(drawOverview) { if(event->button() == Qt::LeftButton) { //Enter scrolling mode this->scroll_base_x = event->x(); this->scroll_base_y = event->y(); this->scroll_mode = true; this->setCursor(Qt::ClosedHandCursor); this->viewport()->grabMouse(); //Scroll to the cursor this->horizontalScrollBar()->setValue(((event->x() - this->overviewXOfs) / this->overviewScale) - this->viewport()->width() / 2); this->verticalScrollBar()->setValue(((event->y() - this->overviewYOfs) / this->overviewScale) - this->viewport()->height() / 2); } /*else if(event->button() == Qt::RightButton) { QMenu wMenu(this); mMenuBuilder->build(&wMenu); wMenu.exec(event->globalPos()); //execute context menu }*/ } else if((event->button() == Qt::LeftButton || event->button() == Qt::RightButton) && this->isMouseEventInBlock(event)) { //Check for click on a token and highlight it Token token; delete this->highlight_token; if(this->getTokenForMouseEvent(event, token)) this->highlight_token = HighlightToken::fromToken(token); else this->highlight_token = nullptr; //Update current instruction duint instr = this->getInstrForMouseEvent(event); if(instr != 0) this->cur_instr = instr; this->viewport()->update(); if(event->button() == Qt::RightButton) { DisassemblyContextMenu cMenu(instr, this); cMenu.exec(event->globalPos()); //execute context menu } } else if(event->button() == Qt::LeftButton) { //Left click outside any block, enter scrolling mode this->scroll_base_x = event->x(); this->scroll_base_y = event->y(); this->scroll_mode = true; this->setCursor(Qt::ClosedHandCursor); this->viewport()->grabMouse(); } else if(event->button() == Qt::RightButton) { /*//Right click outside of block QMenu wMenu(this); mMenuBuilder->build(&wMenu); wMenu.exec(event->globalPos()); //execute context menu */ } } void DisassemblerGraphView::mouseMoveEvent(QMouseEvent* event) { if(this->scroll_mode) { int x_delta = this->scroll_base_x - event->x(); int y_delta = this->scroll_base_y - event->y(); if(drawOverview) { x_delta = -x_delta / this->overviewScale; y_delta = -y_delta / this->overviewScale; } this->scroll_base_x = event->x(); this->scroll_base_y = event->y(); this->horizontalScrollBar()->setValue(this->horizontalScrollBar()->value() + x_delta); this->verticalScrollBar()->setValue(this->verticalScrollBar()->value() + y_delta); } } void DisassemblerGraphView::mouseReleaseEvent(QMouseEvent* event) { if(event->button() == Qt::ForwardButton) gotoNextSlot(); else if(event->button() == Qt::BackButton) gotoPreviousSlot(); if(event->button() != Qt::LeftButton) return; if(this->scroll_mode) { this->scroll_mode = false; this->setCursor(Qt::ArrowCursor); this->viewport()->releaseMouse(); } } void DisassemblerGraphView::mouseDoubleClickEvent(QMouseEvent* event) { Q_UNUSED(event); if(drawOverview) { toggleOverviewSlot(); } else { duint instr = this->getInstrForMouseEvent(event); //DbgCmdExec(QString("graph dis.branchdest(%1), silent").arg(ToPtrString(instr)).toUtf8().constData()); QList refs = Core()->getXRefs(instr, false, false); if (refs.length()) { Core()->seek(refs.at(0).to); } if (refs.length() > 1) { qWarning() << "Too many references here. Weird behaviour expected."; } } } void DisassemblerGraphView::prepareGraphNode(DisassemblerBlock & block) { 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 DisassemblerGraphView::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 DisassemblerGraphView::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 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++; } //Mark chosen index as used for(int col = min_col; col < max_col + 1; col++) this->markEdge(edges, row, col, i); return i; } int DisassemblerGraphView::findVertEdgeIndex(EdgesVector & edges, int col, int min_row, int max_row) { //Find a valid index int i = 0; while(true) { bool valid = true; for(int row = min_row; row < max_row + 1; row++) if(isEdgeMarked(edges, row, col, i)) { valid = false; break; } if(valid) break; i++; } //Mark chosen index as used for(int row = min_row; row < max_row + 1; row++) this->markEdge(edges, row, col, i); return i; } DisassemblerGraphView::DisassemblerEdge DisassemblerGraphView::routeEdge(EdgesVector & horiz_edges, EdgesVector & vert_edges, Matrix & edge_valid, DisassemblerBlock & start, DisassemblerBlock & end, QColor color) { DisassemblerEdge edge; edge.color = color; edge.dest = &end; //Find edge index for initial outgoing line int i = 0; while(true) { if(!this->isEdgeMarked(vert_edges, start.row + 1, start.col + 1, i)) break; i += 1; } this->markEdge(vert_edges, start.row + 1, start.col + 1, i); edge.addPoint(start.row + 1, start.col + 1); edge.start_index = i; bool horiz = false; //Find valid column for moving vertically to the target node int min_row, max_row; if(end.row < (start.row + 1)) { min_row = end.row; max_row = start.row + 1; } else { min_row = start.row + 1; max_row = end.row; } int col = start.col + 1; if(min_row != max_row) { auto checkColumn = [min_row, max_row, &edge_valid](int column) { if(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 DisassemblerGraphView::renderFunction(Function & func) { //Create render nodes this->blocks.clear(); for(Block & block : func.blocks) { this->blocks[block.entry] = DisassemblerBlock(block); this->prepareGraphNode(this->blocks[block.entry]); } //puts("Create render nodes"); //Populate incoming lists for(auto & blockIt : this->blocks) { DisassemblerBlock & block = blockIt.second; for(auto & edge : block.block.exits) this->blocks[edge].incoming.push_back(block.block.entry); } //puts("Populate incoming lists"); //Construct acyclic graph where each node is used as an edge exactly once std::unordered_set visited; visited.insert(func.entry); std::queue queue; queue.push(this->blocks[func.entry].block.entry); std::vector blockOrder; bool changed = true; while(changed) { changed = false; //First pick nodes that have single entry points while(!queue.empty()) { DisassemblerBlock & block = this->blocks[queue.front()]; queue.pop(); blockOrder.push_back(block.block.entry); for(duint edge : block.block.exits) { if(visited.count(edge)) continue; //If node has no more unseen incoming edges, add it to the graph layout now if(int(this->blocks[edge].incoming.size()) == 1) { removeFromVec(this->blocks[edge].incoming, block.block.entry); block.new_exits.push_back(edge); queue.push(this->blocks[edge].block.entry); visited.insert(edge); changed = true; } else { removeFromVec(this->blocks[edge].incoming, block.block.entry); } } } //No more nodes satisfy constraints, pick a node to continue constructing the graph duint best = 0; int best_edges; duint best_parent; for(auto & blockIt : this->blocks) { DisassemblerBlock & block = blockIt.second; if(!visited.count(block.block.entry)) continue; for(duint edge : block.block.exits) { if(visited.count(edge)) continue; if((best == 0) || (int(this->blocks[edge].incoming.size()) < best_edges) || ( (int(this->blocks[edge].incoming.size()) == best_edges) && (edge < best))) { best = edge; best_edges = int(this->blocks[edge].incoming.size()); best_parent = block.block.entry; } } } if(best != 0) { DisassemblerBlock & best_parentb = this->blocks[best_parent]; removeFromVec(this->blocks[best].incoming, best_parentb.block.entry); best_parentb.new_exits.push_back(best); visited.insert(best); queue.push(best); changed = true; } } //puts("Construct acyclic graph where each node is used as an edge exactly once"); //Compute graph layout from bottom up this->computeGraphLayout(this->blocks[func.entry]); //Optimize layout to be more compact /*std::vector rowBlocks; for(auto blockIt : this->blocks) rowBlocks.push_back(blockIt.second); std::sort(rowBlocks.begin(), rowBlocks.end(), [](DisassemblerBlock & a, DisassemblerBlock & b) { if(a.row < b.row) return true; if(a.row == b.row) return a.col < b.col; return false; }); std::vector> rowMap; for(DisassemblerBlock & block : rowBlocks) { if(block.row == rowMap.size()) rowMap.push_back(std::vector()); rowMap[block.row].push_back(block); } int median = this->blocks[func.entry].col; for(auto & blockVec : rowMap) { int len = int(blockVec.size()); if(len == 1) continue; int bestidx = 0; int bestdist = median; for(int i = 0; i < len; i++) { auto & block = blockVec[i]; int dist = std::abs(block.col - median); if(dist < bestdist) { bestdist = dist; bestidx = i; } } for(int j = bestidx - 1; j > -1; j--) blockVec[j].col = blockVec[j + 1].col - 2; for(int j = bestidx + 1; j < len; j++) blockVec[j].col = blockVec[j - 1].col + 2; } for(auto & blockVec : rowMap) for(DisassemblerBlock & block : blockVec) blocks[block.block.entry] = block;*/ //puts("Compute graph layout from bottom up"); //Prepare edge routing EdgesVector horiz_edges, vert_edges; horiz_edges.resize(this->blocks[func.entry].row_count + 1); vert_edges.resize(this->blocks[func.entry].row_count + 1); Matrix edge_valid; edge_valid.resize(this->blocks[func.entry].row_count + 1); for(int row = 0; row < this->blocks[func.entry].row_count + 1; row++) { horiz_edges[row].resize(this->blocks[func.entry].col_count + 1); vert_edges[row].resize(this->blocks[func.entry].col_count + 1); initVec(edge_valid[row], this->blocks[func.entry].col_count + 1, true); for(int col = 0; col < this->blocks[func.entry].col_count + 1; col++) { horiz_edges[row][col].clear(); vert_edges[row][col].clear(); } } for(auto & blockIt : this->blocks) { DisassemblerBlock & block = blockIt.second; edge_valid[block.row][block.col + 1] = false; } //puts("Prepare edge routing"); //Perform edge routing for(duint blockId : blockOrder) { DisassemblerBlock & block = blocks[blockId]; DisassemblerBlock & start = block; for(duint edge : block.block.exits) { DisassemblerBlock & end = this->blocks[edge]; QColor color = jmpColor; if(edge == block.block.true_path) color = brtrueColor; else if(edge == block.block.false_path) color = brfalseColor; start.edges.push_back(this->routeEdge(horiz_edges, vert_edges, edge_valid, start, end, color)); } } //puts("Perform edge routing"); //Compute edge counts for each row and column std::vector col_edge_count, row_edge_count; initVec(col_edge_count, this->blocks[func.entry].col_count + 1, 0); initVec(row_edge_count, this->blocks[func.entry].row_count + 1, 0); for(int row = 0; row < this->blocks[func.entry].row_count + 1; row++) { for(int col = 0; col < this->blocks[func.entry].col_count + 1; col++) { if(int(horiz_edges[row][col].size()) > row_edge_count[row]) row_edge_count[row] = int(horiz_edges[row][col].size()); if(int(vert_edges[row][col].size()) > col_edge_count[col]) col_edge_count[col] = int(vert_edges[row][col].size()); } } //puts("Compute edge counts for each row and column"); //Compute row and column sizes std::vector col_width, row_height; initVec(col_width, this->blocks[func.entry].col_count + 1, 0); initVec(row_height, this->blocks[func.entry].row_count + 1, 0); for(auto & blockIt : this->blocks) { DisassemblerBlock & block = blockIt.second; if((int(block.width / 2)) > col_width[block.col]) col_width[block.col] = int(block.width / 2); if((int(block.width / 2)) > col_width[block.col + 1]) col_width[block.col + 1] = int(block.width / 2); if(int(block.height) > row_height[block.row]) row_height[block.row] = int(block.height); } //puts("Compute row and column sizes"); //Compute row and column positions std::vector col_x, row_y; initVec(col_x, this->blocks[func.entry].col_count, 0); initVec(row_y, this->blocks[func.entry].row_count, 0); initVec(this->col_edge_x, this->blocks[func.entry].col_count + 1, 0); initVec(this->row_edge_y, this->blocks[func.entry].row_count + 1, 0); int x = 16; for(int i = 0; i < this->blocks[func.entry].col_count; i++) { this->col_edge_x[i] = x; x += 8 * col_edge_count[i]; col_x[i] = x; x += col_width[i]; } int y = 16; for(int i = 0; i < this->blocks[func.entry].row_count; i++) { this->row_edge_y[i] = y; y += 8 * row_edge_count[i]; row_y[i] = y; y += row_height[i]; } this->col_edge_x[this->blocks[func.entry].col_count] = x; this->row_edge_y[this->blocks[func.entry].row_count] = y; this->width = x + 16 + (8 * col_edge_count[this->blocks[func.entry].col_count]); this->height = y + 16 + (8 * row_edge_count[this->blocks[func.entry].row_count]); //puts("Compute row and column positions"); //Compute node positions for(auto & blockIt : this->blocks) { DisassemblerBlock & block = blockIt.second; block.x = int( (col_x[block.col] + col_width[block.col] + 4 * col_edge_count[block.col + 1]) - (block.width / 2)); if((block.x + block.width) > ( col_x[block.col] + col_width[block.col] + col_width[block.col + 1] + 8 * col_edge_count[ block.col + 1])) { block.x = int((col_x[block.col] + col_width[block.col] + col_width[block.col + 1] + 8 * col_edge_count[ block.col + 1]) - block.width); } block.y = row_y[block.row]; } //puts("Compute node positions"); //Precompute coordinates for edges for(auto & blockIt : this->blocks) { DisassemblerBlock & block = blockIt.second; for(DisassemblerEdge & edge : block.edges) { auto start = edge.points[0]; auto start_col = start.col; auto last_index = edge.start_index; auto last_pt = QPoint(this->col_edge_x[start_col] + (8 * last_index) + 4, block.y + block.height + 4 - (2 * this->charWidth)); QPolygonF pts; pts.append(last_pt); for(int i = 0; i < int(edge.points.size()); i++) { auto end = edge.points[i]; auto end_row = end.row; auto end_col = end.col; auto last_index = end.index; QPoint new_pt; if(start_col == end_col) new_pt = QPoint(last_pt.x(), this->row_edge_y[end_row] + (8 * last_index) + 4); else new_pt = QPoint(this->col_edge_x[end_col] + (8 * last_index) + 4, last_pt.y()); pts.push_back(new_pt); last_pt = new_pt; start_col = end_col; } auto new_pt = QPoint(last_pt.x(), edge.dest->y + this->charWidth - 1); pts.push_back(new_pt); edge.polyline = pts; pts.clear(); pts.append(QPoint(new_pt.x() - 3, new_pt.y() - 6)); pts.append(QPoint(new_pt.x() + 3, new_pt.y() - 6)); pts.append(new_pt); edge.arrow = pts; } } //puts("Precompute coordinates for edges"); //Adjust scroll bars for new size auto areaSize = this->viewport()->size(); this->adjustSize(areaSize.width(), areaSize.height()); //puts("Adjust scroll bars for new size"); if(this->desired_pos) { //There was a position saved, navigate to it this->horizontalScrollBar()->setValue(this->desired_pos[0]); this->verticalScrollBar()->setValue(this->desired_pos[1]); } else if(this->cur_instr != 0) { this->show_cur_instr(this->forceCenter); this->forceCenter = false; } else { //Ensure start node is visible auto start_x = this->blocks[func.entry].x + this->renderXOfs + int(this->blocks[func.entry].width / 2); this->horizontalScrollBar()->setValue(start_x - int(areaSize.width() / 2)); this->verticalScrollBar()->setValue(0); } this->analysis.update_id = this->update_id = func.update_id; this->ready = true; this->viewport()->update(0, 0, areaSize.width(), areaSize.height()); //puts("Finished"); } void DisassemblerGraphView::updateTimerEvent() { qDebug() << "Update timer"; on_seekChanged(0); return; // TODO: Remove it if not used anymore //qDebug() << status << this->status << this->function << this->ready << this->update_id << this->analysis.update_id; // TODO status is useless (for now at least) auto status = this->analysis.status; if(status != this->status) { this->status = status; this->viewport()->update(); } if(this->function == 0) { loadCurrentGraph(); return; } if(this->ready) { //Check for updated code if(this->update_id != this->analysis.update_id) this->renderFunction(this->analysis.functions[this->function]); return; } //View not up to date, check to see if active function is ready if(this->analysis.functions.count(this->function)) { if(this->analysis.functions[this->function].ready) { //Active function now ready, generate graph this->renderFunction(this->analysis.functions[this->function]); } } } void DisassemblerGraphView::show_cur_instr(bool force) { for(auto & blockIt : this->blocks) { DisassemblerBlock & block = blockIt.second; auto row = int(block.block.header_text.lines.size()); for(Instr & instr : block.block.instrs) { if(this->cur_instr == instr.addr) { //Don't update the view for blocks that are already fully in view int xofs = this->horizontalScrollBar()->value(); int yofs = this->verticalScrollBar()->value(); QRect viewportRect = this->viewport()->rect(); QPoint translation(this->renderXOfs - xofs, this->renderYOfs - yofs); viewportRect.translate(-translation.x(), -translation.y()); if(force || !viewportRect.contains(QRect(block.x + this->charWidth, block.y + this->charWidth, block.width - (2 * this->charWidth), block.height - (2 * this->charWidth)))) { auto x = block.x + int(block.width / 2); auto y = block.y + (2 * this->charWidth) + int((row + 0.5) * this->charHeight); this->horizontalScrollBar()->setValue(x + this->renderXOfs - int(this->horizontalScrollBar()->pageStep() / 2)); this->verticalScrollBar()->setValue(y + this->renderYOfs - int(this->verticalScrollBar()->pageStep() / 2)); } return; } row += int(instr.text.lines.size()); } } } bool DisassemblerGraphView::navigate(duint addr) { //Add address to history //if(!mHistoryLock) // mHistory.addVaToHistory(addr); //Check to see if address is within current function for(auto & blockIt : this->blocks) { DisassemblerBlock & block = blockIt.second; if(block.block.entry > addr) //optimize it continue; auto row = int(block.block.header_text.lines.size()); for(Instr & instr : block.block.instrs) { if((addr >= instr.addr) && (addr < (instr.addr + int(instr.opcode.size())))) { this->cur_instr = instr.addr; this->show_cur_instr(); this->viewport()->update(); return true; } row += int(instr.text.lines.size()); } } //Check other functions for this address duint func, instr; if(this->analysis.find_instr(addr, func, instr)) { this->function = func; this->cur_instr = instr; this->highlight_token = nullptr; this->ready = false; this->desired_pos = nullptr; this->viewport()->update(); return true; } return false; } void DisassemblerGraphView::fontChanged() { this->initFont(); if(this->ready) { //Rerender function to update layout this->renderFunction(this->analysis.functions[this->function]); } } void DisassemblerGraphView::setGraphLayout(DisassemblerGraphView::LayoutType layout) { this->layoutType = layout; if(this->ready) { this->renderFunction(this->analysis.functions[this->function]); } } void DisassemblerGraphView::tokenizerConfigUpdatedSlot() { //disasm.UpdateConfig(); loadCurrentGraph(); } void DisassemblerGraphView::loadCurrentGraph() { // Read functions QJsonDocument functionsDoc = Core()->cmdj("agj"); QJsonArray functions = functionsDoc.array(); Analysis anal; anal.ready = true; anal.update_id = this->update_id + 1; QJsonValue funcRef = functions.first(); QJsonObject func = funcRef.toObject(); Function f; f.ready = true; f.entry = func["offset"].toInt(); f.update_id = anal.update_id; for (QJsonValueRef blockRef : func["blocks"].toArray()) { QJsonObject block = blockRef.toObject(); duint fail; duint jump; /* Parse Block data */ Block b; b.entry = block["offset"].toInt(); fail = block["fail"].toInt(); if (fail) { b.false_path = fail; b.exits.push_back(fail); } jump = block["jump"].toInt(); if (jump) { if (fail) { b.true_path = jump; } b.exits.push_back(jump); } for (QJsonValueRef opRef : block["ops"].toArray()) { QJsonObject op = opRef.toObject(); Instr i; i.addr = op["offset"].toInt(); RichTextPainter::List richText; Colors::colorizeAssembly(richText, op["opcode"].toString(), op["type_num"].toVariant().toULongLong()); if (op["comment"].toString().length()) { RichTextPainter::CustomRichText_t comment; comment.text = QString(" ; %1").arg(QByteArray::fromBase64(op["comment"].toString().toLocal8Bit()).data()); comment.textColor = ConfigColor("comment"); comment.flags = RichTextPainter::FlagColor; richText.insert(richText.end(), comment); } i.text = Text(richText); b.instrs.push_back(i); } f.blocks.push_back(b); } anal.functions[f.entry] = f; anal.status = "Ready."; anal.entry = f.entry; this->analysis = anal; this->function = this->analysis.entry; this->ready = true; } /*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::on_seekChanged(RVA addr) { Q_UNUSED(addr); loadCurrentGraph(); Function f = this->analysis.functions[this->function]; if (f.blocks.size() > 0) { Core()->graphDisplay = true; this->parentWidget()->raise(); } else { Core()->graphDisplay = false; } this->renderFunction(this->analysis.functions[this->function]); } void DisassemblerGraphView::graphAtSlot(duint addr) { Q_UNUSED(addr); //Bridge::getBridge()->setResult(this->navigate(addr) ? this->currentGraph.entryPoint : 0); } void DisassemblerGraphView::updateGraphSlot() { //this->viewport()->update(); } void DisassemblerGraphView::addReferenceAction(QMenu* menu, duint addr) { Q_UNUSED(menu); Q_UNUSED(addr); /*QAction* action = new QAction(menu); action->setData(ToPtrString(addr)); action->setText(getSymbolicName(addr)); connect(action, SIGNAL(triggered()), this, SLOT(followActionSlot())); menu->addAction(action); */ } void DisassemblerGraphView::setupContextMenu() { // TODO make this prettier QShortcut *shortcut_x = new QShortcut(QKeySequence(Qt::Key_X), this); shortcut_x->setContext(Qt::WidgetShortcut); connect(shortcut_x, SIGNAL(activated()), this, SLOT(xrefSlot())); QShortcut *shortcut_escape = new QShortcut(QKeySequence(Qt::Key_Escape), this); shortcut_escape->setContext(Qt::WidgetShortcut); connect(shortcut_escape, SIGNAL(activated()), this, SLOT(seekPrev())); /* 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) { Q_UNUSED(event); /* if(event->modifiers() != 0) return; int key = event->key(); if(key == Qt::Key_Up) DbgCmdExec(QString("graph dis.prev(%1), silent").arg(ToPtrString(cur_instr)).toUtf8().constData()); else if(key == Qt::Key_Down) DbgCmdExec(QString("graph dis.next(%1), silent").arg(ToPtrString(cur_instr)).toUtf8().constData()); else if(key == Qt::Key_Left) DbgCmdExec(QString("graph dis.brtrue(%1), silent").arg(ToPtrString(cur_instr)).toUtf8().constData()); else if(key == Qt::Key_Right) DbgCmdExec(QString("graph dis.brfalse(%1), silent").arg(ToPtrString(cur_instr)).toUtf8().constData()); if(key == Qt::Key_Return || key == Qt::Key_Enter) DbgCmdExec(QString("graph dis.branchdest(%1), silent").arg(ToPtrString(cur_instr)).toUtf8().constData()); */ } void DisassemblerGraphView::followDisassemblerSlot() { //DbgCmdExec(QString("disasm %1").arg(ToPtrString(this->cur_instr)).toUtf8().constData()); } void DisassemblerGraphView::colorsUpdatedSlot() { disassemblyBackgroundColor = ConfigColor("gui.background"); graphNodeColor = ConfigColor("gui.border"); backgroundColor = ConfigColor("gui.alt_background"); disassemblySelectionColor = ConfigColor("gui.highlight"); jmpColor = ConfigColor("graph.trufae"); brtrueColor = ConfigColor("graph.true"); brfalseColor = ConfigColor("graph.false"); /*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 DisassemblerGraphView::fontsUpdatedSlot() { fontChanged(); } void DisassemblerGraphView::shortcutsUpdatedSlot() { //updateShortcuts(); } void DisassemblerGraphView::toggleOverviewSlot() { drawOverview = !drawOverview; if(onlySummary) { onlySummary = false; mToggleSummary->setChecked(false); loadCurrentGraph(); } else this->viewport()->update(); } void DisassemblerGraphView::toggleSummarySlot() { drawOverview = false; onlySummary = !onlySummary; loadCurrentGraph(); } /*void DisassemblerGraphView::selectionGetSlot(SELECTIONDATA* selection) { selection->start = selection->end = cur_instr; Bridge::getBridge()->setResult(1); }*/ /*void DisassemblerGraphView::disassembleAtSlot(dsint va, dsint cip) { Q_UNUSED(va); auto cipChanged = mCip != cip; mCip = cip; if(syncOrigin && cipChanged) gotoOriginSlot(); else this->viewport()->update(); }*/ void DisassemblerGraphView::gotoExpressionSlot() { /*if(!DbgIsDebugging()) return; if(!mGoto) mGoto = new GotoDialog(this); if(mGoto->exec() == QDialog::Accepted) { 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)); 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 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) { 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 DisassemblerGraphView::xrefSlot() { RVA addr = this->get_cursor_pos(); XrefsDialog *dialog = new XrefsDialog(this); dialog->fillRefsForAddress(addr, RAddressString(addr), false); dialog->exec(); } 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()); } */ } void DisassemblerGraphView::seekPrev() { Core()->seekPrev(); }