cutter/src/widgets/DisassemblerGraphView.cpp

953 lines
30 KiB
C++
Raw Normal View History

2017-10-09 09:38:57 +00:00
#include "DisassemblerGraphView.h"
#include "common/CutterSeekable.h"
#include "core/Cutter.h"
#include "core/MainWindow.h"
#include "common/Colors.h"
#include "common/Configuration.h"
#include "common/TempConfig.h"
#include "common/SyntaxHighlighter.h"
#include "common/BasicBlockHighlighter.h"
#include "common/BasicInstructionHighlighter.h"
#include "common/Helpers.h"
2019-05-23 16:22:31 +00:00
#include <QColorDialog>
#include <QPainter>
#include <QJsonObject>
#include <QJsonArray>
#include <QMouseEvent>
#include <QPropertyAnimation>
#include <QShortcut>
#include <QToolTip>
2018-02-01 14:15:16 +00:00
#include <QTextDocument>
#include <QTextEdit>
#include <QVBoxLayout>
#include <QRegularExpression>
#include <QClipboard>
#include <QApplication>
#include <QAction>
2017-10-09 09:38:57 +00:00
#include <cmath>
2017-10-09 09:38:57 +00:00
2019-09-19 05:19:50 +00:00
DisassemblerGraphView::DisassemblerGraphView(QWidget *parent, CutterSeekable *seekable,
MainWindow *mainWindow, QList<QAction *> additionalMenuActions)
: CutterGraphView(parent),
blockMenu(new DisassemblyContextMenu(this, mainWindow)),
contextMenu(new QMenu(this)),
seekable(seekable),
actionUnhighlight(this),
actionUnhighlightInstruction(this)
2017-10-09 09:38:57 +00:00
{
2017-12-14 21:07:48 +00:00
highlight_token = nullptr;
auto *layout = new QVBoxLayout(this);
// Signals that require a refresh all
connect(Core(), SIGNAL(refreshAll()), this, SLOT(refreshView()));
connect(Core(), SIGNAL(commentsChanged()), this, SLOT(refreshView()));
connect(Core(), &CutterCore::functionRenamed, this, &DisassemblerGraphView::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(), &CutterCore::breakpointsChanged, this, &DisassemblerGraphView::refreshView);
connect(Core(), SIGNAL(functionsChanged()), this, SLOT(refreshView()));
2018-01-31 09:17:06 +00:00
connect(Core(), SIGNAL(asmOptionsChanged()), this, SLOT(refreshView()));
connect(Core(), SIGNAL(refreshCodeViews()), this, SLOT(refreshView()));
2017-10-09 09:38:57 +00:00
connectSeekChanged(false);
2017-10-09 09:38:57 +00:00
// ESC for previous
QShortcut *shortcut_escape = new QShortcut(QKeySequence(Qt::Key_Escape), this);
shortcut_escape->setContext(Qt::WidgetShortcut);
connect(shortcut_escape, &QShortcut::activated, seekable, &CutterSeekable::seekPrev);
2017-10-09 09:38:57 +00:00
2017-12-13 22:57:36 +00:00
// Branch shortcuts
QShortcut *shortcut_take_true = new QShortcut(QKeySequence(Qt::Key_T), this);
shortcut_take_true->setContext(Qt::WidgetShortcut);
connect(shortcut_take_true, &QShortcut::activated, this, &DisassemblerGraphView::takeTrue);
QShortcut *shortcut_take_false = new QShortcut(QKeySequence(Qt::Key_F), this);
shortcut_take_false->setContext(Qt::WidgetShortcut);
connect(shortcut_take_false, &QShortcut::activated, this, &DisassemblerGraphView::takeFalse);
2017-12-14 21:07:48 +00:00
// Navigation shortcuts
QShortcut *shortcut_next_instr = new QShortcut(QKeySequence(Qt::Key_J), this);
shortcut_next_instr->setContext(Qt::WidgetShortcut);
connect(shortcut_next_instr, &QShortcut::activated, this, &DisassemblerGraphView::nextInstr);
2017-12-14 21:07:48 +00:00
QShortcut *shortcut_prev_instr = new QShortcut(QKeySequence(Qt::Key_K), this);
shortcut_prev_instr->setContext(Qt::WidgetShortcut);
connect(shortcut_prev_instr, &QShortcut::activated, this, &DisassemblerGraphView::prevInstr);
2017-12-14 21:07:48 +00:00
shortcuts.append(shortcut_escape);
shortcuts.append(shortcut_next_instr);
shortcuts.append(shortcut_prev_instr);
// Context menu that applies to everything
contextMenu->addAction(&actionExportGraph);
contextMenu->addMenu(layoutMenu);
contextMenu->addSeparator();
contextMenu->addActions(additionalMenuActions);
2019-05-23 16:22:31 +00:00
QAction *highlightBB = new QAction(this);
actionUnhighlight.setVisible(false);
highlightBB->setText(tr("Highlight block"));
connect(highlightBB, &QAction::triggered, this, [this]() {
auto bbh = Core()->getBBHighlighter();
RVA currBlockEntry = blockForAddress(this->seekable->getOffset())->entry;
QColor background = disassemblyBackgroundColor;
if (auto block = bbh->getBasicBlock(currBlockEntry)) {
background = block->color;
}
QColor c = QColorDialog::getColor(background, this, QString(),
QColorDialog::DontUseNativeDialog);
if (c.isValid()) {
bbh->highlight(currBlockEntry, c);
}
Config()->colorsUpdated();
});
actionUnhighlight.setText(tr("Unhighlight block"));
connect(&actionUnhighlight, &QAction::triggered, this, [this]() {
auto bbh = Core()->getBBHighlighter();
bbh->clear(blockForAddress(this->seekable->getOffset())->entry);
Config()->colorsUpdated();
});
QAction *highlightBI = new QAction(this);
actionUnhighlightInstruction.setVisible(false);
highlightBI->setText(tr("Highlight instruction"));
connect(highlightBI, &QAction::triggered, this,
&DisassemblerGraphView::onActionHighlightBITriggered);
actionUnhighlightInstruction.setText(tr("Unhighlight instruction"));
connect(&actionUnhighlightInstruction, &QAction::triggered, this,
&DisassemblerGraphView::onActionUnhighlightBITriggered);
2019-05-23 16:22:31 +00:00
blockMenu->addAction(highlightBB);
blockMenu->addAction(&actionUnhighlight);
blockMenu->addAction(highlightBI);
blockMenu->addAction(&actionUnhighlightInstruction);
2019-05-23 16:22:31 +00:00
// Include all actions from generic context menu in block specific menu
blockMenu->addSeparator();
blockMenu->addActions(contextMenu->actions());
connect(blockMenu, &DisassemblyContextMenu::copy, this, &DisassemblerGraphView::copySelection);
// Add header as widget to layout so it stretches to the layout width
layout->setContentsMargins(0, 0, 0, 0);
layout->setAlignment(Qt::AlignTop);
this->scale_thickness_multiplier = true;
2017-10-09 09:38:57 +00:00
}
void DisassemblerGraphView::connectSeekChanged(bool disconn)
{
if (disconn) {
disconnect(seekable, &CutterSeekable::seekableSeekChanged, this,
2018-09-30 20:00:53 +00:00
&DisassemblerGraphView::onSeekChanged);
} else {
connect(seekable, &CutterSeekable::seekableSeekChanged, this,
&DisassemblerGraphView::onSeekChanged);
}
}
2017-12-14 21:07:48 +00:00
DisassemblerGraphView::~DisassemblerGraphView()
{
2018-03-21 20:32:32 +00:00
for (QShortcut *shortcut : shortcuts) {
2017-12-14 21:07:48 +00:00
delete shortcut;
}
}
void DisassemblerGraphView::refreshView()
2017-10-09 09:38:57 +00:00
{
CutterGraphView::refreshView();
2017-10-09 09:38:57 +00:00
loadCurrentGraph();
emit viewRefreshed();
2017-10-09 09:38:57 +00:00
}
void DisassemblerGraphView::loadCurrentGraph()
{
2018-02-01 14:15:16 +00:00
TempConfig tempConfig;
2019-02-14 21:47:39 +00:00
tempConfig.set("scr.color", COLOR_MODE_16M)
.set("asm.bb.line", false)
2018-03-21 20:32:32 +00:00
.set("asm.lines", false)
2018-05-24 06:21:12 +00:00
.set("asm.lines.fcn", false);
2018-12-24 12:22:46 +00:00
QJsonArray functions;
RAnalFunction *fcn = Core()->functionIn(seekable->getOffset());
2018-12-24 12:22:46 +00:00
if (fcn) {
currentFcnAddr = fcn->addr;
2018-12-24 12:22:46 +00:00
QJsonDocument functionsDoc = Core()->cmdj("agJ " + RAddressString(fcn->addr));
functions = functionsDoc.array();
}
2017-10-09 09:38:57 +00:00
disassembly_blocks.clear();
2017-12-14 21:07:48 +00:00
blocks.clear();
if (highlight_token) {
delete highlight_token;
highlight_token = nullptr;
}
emptyGraph = functions.isEmpty();
if (emptyGraph) {
// If there's no function to print, just add a message
if (!emptyText) {
emptyText = new QLabel(this);
emptyText->setText(tr("No function detected. Cannot display graph."));
emptyText->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
layout()->addWidget(emptyText);
layout()->setAlignment(emptyText, Qt::AlignHCenter);
}
emptyText->setVisible(true);
} else if (emptyText) {
emptyText->setVisible(false);
}
// Refresh global "empty graph" variable so other widget know there is nothing to show here
Core()->setGraphEmpty(emptyGraph);
2017-10-09 09:38:57 +00:00
QJsonValue funcRef = functions.first();
QJsonObject func = funcRef.toObject();
2017-11-21 17:48:01 +00:00
windowTitle = tr("Graph");
2017-11-21 17:48:01 +00:00
QString funcName = func["name"].toString().trimmed();
if (emptyGraph) {
windowTitle += " (Empty)";
} else if (!funcName.isEmpty()) {
2017-11-21 17:48:01 +00:00
windowTitle += " (" + funcName + ")";
}
emit nameChanged(windowTitle);
2017-10-09 09:38:57 +00:00
RVA entry = func["offset"].toVariant().toULongLong();
setEntry(entry);
for (const QJsonValueRef &value : func["blocks"].toArray()) {
QJsonObject block = value.toObject();
RVA block_entry = block["offset"].toVariant().toULongLong();
2018-02-01 14:15:16 +00:00
RVA block_size = block["size"].toVariant().toULongLong();
RVA block_fail = block["fail"].toVariant().toULongLong();
RVA block_jump = block["jump"].toVariant().toULongLong();
2017-10-09 09:38:57 +00:00
DisassemblyBlock db;
GraphBlock gb;
gb.entry = block_entry;
db.entry = block_entry;
db.true_path = RVA_INVALID;
db.false_path = RVA_INVALID;
2018-03-21 20:32:32 +00:00
if (block_fail) {
db.false_path = block_fail;
gb.edges.emplace_back(block_fail);
2017-10-09 09:38:57 +00:00
}
2018-03-21 20:32:32 +00:00
if (block_jump) {
if (block_fail) {
db.true_path = block_jump;
2017-10-12 12:14:33 +00:00
}
gb.edges.emplace_back(block_jump);
2017-10-09 09:38:57 +00:00
}
2018-09-20 11:27:24 +00:00
QJsonObject switchOp = block["switchop"].toObject();
if (!switchOp.isEmpty()) {
QJsonArray caseArray = switchOp["cases"].toArray();
for (QJsonValue caseOpValue : caseArray) {
QJsonObject caseOp = caseOpValue.toObject();
bool ok;
RVA caseJump = caseOp["jump"].toVariant().toULongLong(&ok);
if (!ok) {
continue;
}
gb.edges.emplace_back(caseJump);
2018-09-20 11:27:24 +00:00
}
}
2018-02-01 14:15:16 +00:00
QJsonArray opArray = block["ops"].toArray();
2018-03-21 20:32:32 +00:00
for (int opIndex = 0; opIndex < opArray.size(); opIndex++) {
2018-02-01 14:15:16 +00:00
QJsonObject op = opArray[opIndex].toObject();
2017-10-09 09:38:57 +00:00
Instr i;
i.addr = op["offset"].toVariant().toULongLong();
2018-01-31 09:17:06 +00:00
2018-03-21 20:32:32 +00:00
if (opIndex < opArray.size() - 1) {
2018-02-01 14:15:16 +00:00
// get instruction size from distance to next instruction ...
2018-03-21 20:32:32 +00:00
RVA nextOffset = opArray[opIndex + 1].toObject()["offset"].toVariant().toULongLong();
2018-02-01 14:15:16 +00:00
i.size = nextOffset - i.addr;
2018-03-21 20:32:32 +00:00
} else {
2018-02-01 14:15:16 +00:00
// or to the end of the block.
i.size = (block_entry + block_size) - i.addr;
2017-10-14 11:00:23 +00:00
}
2018-01-31 09:17:06 +00:00
2018-03-21 20:32:32 +00:00
QTextDocument textDoc;
2019-02-15 12:33:23 +00:00
textDoc.setHtml(CutterCore::ansiEscapeToHtml(op["text"].toString()));
2019-02-14 21:47:39 +00:00
i.plainText = textDoc.toPlainText();
2018-01-31 09:17:06 +00:00
2018-02-01 14:51:03 +00:00
RichTextPainter::List richText = RichTextPainter::fromTextDocument(textDoc);
//Colors::colorizeAssembly(richText, textDoc.toPlainText(), 0);
2018-01-31 09:17:06 +00:00
bool cropped;
2018-03-21 20:32:32 +00:00
int blockLength = Config()->getGraphBlockMaxChars() + Core()->getConfigb("asm.bytes") * 24 +
Core()->getConfigb("asm.emu") * 10;
2018-01-31 09:59:01 +00:00
i.text = Text(RichTextPainter::cropped(richText, blockLength, "...", &cropped));
2018-03-21 20:32:32 +00:00
if (cropped)
i.fullText = richText;
else
i.fullText = Text();
db.instrs.push_back(i);
2017-10-09 09:38:57 +00:00
}
disassembly_blocks[db.entry] = db;
prepareGraphNode(gb);
addBlock(gb);
2017-10-09 09:38:57 +00:00
}
cleanupEdges(blocks);
if (!func["blocks"].toArray().isEmpty()) {
computeGraphPlacement();
}
}
2017-10-09 09:38:57 +00:00
DisassemblerGraphView::EdgeConfigurationMapping DisassemblerGraphView::getEdgeConfigurations()
{
EdgeConfigurationMapping result;
for (auto &block : blocks) {
for (const auto &edge : block.second.edges) {
2019-09-19 05:19:50 +00:00
result[ {block.first, edge.target}] = edgeConfiguration(block.second, &blocks[edge.target], false);
}
}
return result;
}
void DisassemblerGraphView::prepareGraphNode(GraphBlock &block)
2017-10-09 09:38:57 +00:00
{
DisassemblyBlock &db = disassembly_blocks[block.entry];
int width = 0;
int height = 0;
2018-03-21 20:32:32 +00:00
for (auto &line : db.header_text.lines) {
int lw = 0;
2018-03-21 20:32:32 +00:00
for (auto &part : line)
lw += mFontMetrics->width(part.text);
2018-03-21 20:32:32 +00:00
if (lw > width)
width = lw;
height += 1;
}
2018-03-21 20:32:32 +00:00
for (Instr &instr : db.instrs) {
for (auto &line : instr.text.lines) {
int lw = 0;
2018-03-21 20:32:32 +00:00
for (auto &part : line)
lw += mFontMetrics->width(part.text);
2018-03-21 20:32:32 +00:00
if (lw > width)
width = lw;
height += 1;
2017-10-09 09:38:57 +00:00
}
}
int extra = static_cast<int>(4 * charWidth + 4);
block.width = static_cast<int>(width + extra + charWidth);
2017-12-14 21:07:48 +00:00
block.height = (height * charHeight) + extra;
2017-10-09 09:38:57 +00:00
}
2019-09-19 05:19:50 +00:00
void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block, bool interactive)
2017-10-09 09:38:57 +00:00
{
2019-09-19 05:19:50 +00:00
QRectF blockRect(block.x, block.y, block.width, block.height);
const qreal padding = 2 * charWidth;
p.setPen(Qt::black);
p.setBrush(Qt::gray);
p.setFont(Config()->getFont());
2019-09-19 05:19:50 +00:00
p.drawRect(blockRect);
2017-10-09 09:38:57 +00:00
breakpoints = Core()->getBreakpointsAddresses();
2017-10-09 09:38:57 +00:00
// Render node
DisassemblyBlock &db = disassembly_blocks[block.entry];
bool block_selected = false;
RVA selected_instruction = RVA_INVALID;
2017-10-09 09:38:57 +00:00
// Figure out if the current block is selected
RVA addr = seekable->getOffset();
RVA PCAddr = Core()->getProgramCounterValue();
2018-03-21 20:32:32 +00:00
for (const Instr &instr : db.instrs) {
2019-09-19 05:19:50 +00:00
if (instr.contains(addr) && interactive) {
block_selected = true;
selected_instruction = instr.addr;
}
// TODO: L219
}
2017-10-16 19:00:47 +00:00
p.setPen(QColor(0, 0, 0, 0));
2018-03-21 20:32:32 +00:00
if (db.terminal) {
p.setBrush(retShadowColor);
2018-03-21 20:32:32 +00:00
} else if (db.indirectcall) {
p.setBrush(indirectcallShadowColor);
} else {
p.setBrush(QColor(0, 0, 0, 100));
}
p.setPen(QPen(graphNodeColor, 1));
2018-03-21 20:32:32 +00:00
if (block_selected) {
p.setBrush(disassemblySelectedBackgroundColor);
} else {
p.setBrush(disassemblyBackgroundColor);
}
2019-02-19 18:56:59 +00:00
// Draw basic block background
2019-09-19 05:19:50 +00:00
p.drawRect(blockRect);
2019-02-19 18:56:59 +00:00
auto bb = Core()->getBBHighlighter()->getBasicBlock(block.entry);
if (bb) {
QColor color(bb->color);
p.setBrush(color);
2019-09-19 05:19:50 +00:00
p.drawRect(blockRect);
2019-02-19 18:56:59 +00:00
}
2019-09-19 05:19:50 +00:00
const int firstInstructionY = block.y + getInstructionOffset(db, 0).y();
// Stop rendering text when it's too small
auto transform = p.combinedTransform();
QRect screenChar = transform.mapRect(QRect(0, 0, charWidth, charHeight));
if (screenChar.width() * qhelpers::devicePixelRatio(p.device()) < 4) {
2019-09-19 05:19:50 +00:00
return;
}
2019-02-19 18:56:59 +00:00
// Highlight selected tokens
if (interactive && highlight_token != nullptr) {
int y = firstInstructionY;
qreal tokenWidth = mFontMetrics->width(highlight_token->content);
for (const Instr &instr : db.instrs) {
int pos = -1;
while ((pos = instr.plainText.indexOf(highlight_token->content, pos + 1)) != -1) {
int tokenEnd = pos + highlight_token->content.length();
if ((pos > 0 && instr.plainText[pos - 1].isLetterOrNumber())
2018-09-30 20:00:53 +00:00
|| (tokenEnd < instr.plainText.length() && instr.plainText[tokenEnd].isLetterOrNumber())) {
continue;
}
qreal widthBefore = mFontMetrics->width(instr.plainText.left(pos));
if (charWidth * 3 + widthBefore > block.width - (10 + padding)) {
continue;
}
qreal highlightWidth = tokenWidth;
if (charWidth * 3 + widthBefore + tokenWidth >= block.width - (10 + padding)) {
highlightWidth = block.width - widthBefore - (10 + 2 * padding);
}
2019-05-23 16:22:31 +00:00
QColor selectionColor = ConfigColor("wordHighlight");
2018-12-03 11:45:49 +00:00
2019-09-19 05:19:50 +00:00
p.fillRect(QRectF(block.x + charWidth * 3 + widthBefore, y, highlightWidth,
charHeight), selectionColor);
}
y += int(instr.text.lines.size()) * charHeight;
}
}
// Render node text
2019-09-19 05:19:50 +00:00
auto x = block.x + padding;
int y = block.y + getTextOffset(0).y();
2018-03-21 20:32:32 +00:00
for (auto &line : db.header_text.lines) {
RichTextPainter::paintRichText<qreal>(&p, x, y, block.width, charHeight, 0, line,
2019-09-19 05:19:50 +00:00
mFontMetrics.get());
2017-12-14 21:07:48 +00:00
y += charHeight;
}
auto bih = Core()->getBIHighlighter();
for (const Instr &instr : db.instrs) {
const QRect instrRect = QRect(static_cast<int>(block.x + charWidth), y,
static_cast<int>(block.width - (10 + padding)),
int(instr.text.lines.size()) * charHeight);
QColor instrColor;
if (Core()->isBreakpoint(breakpoints, instr.addr)) {
instrColor = ConfigColor("gui.breakpoint_background");
} else if (instr.addr == PCAddr) {
instrColor = PCSelectionColor;
} else if (auto background = bih->getBasicInstruction(instr.addr)) {
instrColor = background->color;
}
if (instrColor.isValid()) {
p.fillRect(instrRect, instrColor);
}
if (selected_instruction != RVA_INVALID && selected_instruction == instr.addr) {
p.fillRect(instrRect, disassemblySelectionColor);
}
2018-03-21 20:32:32 +00:00
for (auto &line : instr.text.lines) {
2017-12-14 21:07:48 +00:00
int rectSize = qRound(charWidth);
2018-03-21 20:32:32 +00:00
if (rectSize % 2) {
rectSize++;
}
// Assume charWidth <= charHeight
// TODO: Breakpoint/Cip stuff
QRectF bpRect(x - rectSize / 3.0, y + (charHeight - rectSize) / 2.0, rectSize, rectSize);
Q_UNUSED(bpRect);
RichTextPainter::paintRichText<qreal>(&p, x + charWidth, y,
2019-09-19 05:19:50 +00:00
block.width - charWidth, charHeight, 0, line,
mFontMetrics.get());
2017-12-14 21:07:48 +00:00
y += charHeight;
}
}
2017-10-09 09:38:57 +00:00
}
2018-03-21 20:32:32 +00:00
GraphView::EdgeConfiguration DisassemblerGraphView::edgeConfiguration(GraphView::GraphBlock &from,
2019-09-19 05:19:50 +00:00
GraphView::GraphBlock *to,
bool interactive)
2017-10-09 09:38:57 +00:00
{
EdgeConfiguration ec;
DisassemblyBlock &db = disassembly_blocks[from.entry];
2018-03-21 20:32:32 +00:00
if (to->entry == db.true_path) {
ec.color = brtrueColor;
2018-03-21 20:32:32 +00:00
} else if (to->entry == db.false_path) {
ec.color = brfalseColor;
2018-03-21 20:32:32 +00:00
} else {
ec.color = jmpColor;
}
ec.start_arrow = false;
ec.end_arrow = true;
2019-09-19 05:19:50 +00:00
if (interactive) {
if (from.entry == currentBlockAddress) {
ec.width_scale = 2.0;
} else if (to->entry == currentBlockAddress) {
ec.width_scale = 2.0;
}
}
return ec;
2017-10-09 09:38:57 +00:00
}
RVA DisassemblerGraphView::getAddrForMouseEvent(GraphBlock &block, QPoint *point)
2017-10-09 09:38:57 +00:00
{
DisassemblyBlock &db = disassembly_blocks[block.entry];
2017-12-14 21:07:48 +00:00
// Remove header and margin
int off_y = getInstructionOffset(db, 0).y();
2017-12-14 21:07:48 +00:00
// 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 = static_cast<int>(db.header_text.lines.size());
2018-03-21 20:32:32 +00:00
if (mouse_row < cur_row) {
return db.entry;
}
Instr *instr = getInstrForMouseEvent(block, point);
2018-03-21 20:32:32 +00:00
if (instr) {
return instr->addr;
}
return RVA_INVALID;
}
2018-03-21 20:32:32 +00:00
DisassemblerGraphView::Instr *DisassemblerGraphView::getInstrForMouseEvent(
GraphView::GraphBlock &block, QPoint *point, bool force)
{
DisassemblyBlock &db = disassembly_blocks[block.entry];
// Remove header and margin
int off_y = getInstructionOffset(db, 0).y();
// 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 = static_cast<int>(db.header_text.lines.size());
2018-03-21 20:32:32 +00:00
for (Instr &instr : db.instrs) {
if (mouse_row < cur_row + (int)instr.text.lines.size()) {
return &instr;
}
cur_row += instr.text.lines.size();
}
if (force && !db.instrs.empty()) {
if (mouse_row <= 0) {
return &db.instrs.front();
} else {
return &db.instrs.back();
}
}
2017-10-09 09:38:57 +00:00
return nullptr;
}
QRectF DisassemblerGraphView::getInstrRect(GraphView::GraphBlock &block, RVA addr) const
{
auto blockIt = disassembly_blocks.find(block.entry);
if (blockIt == disassembly_blocks.end()) {
return QRectF();
}
auto &db = blockIt->second;
if (db.instrs.empty()) {
return QRectF();
}
size_t sequenceAddr = db.instrs[0].addr;
size_t firstLineWithAddr = 0;
size_t currentLine = 0;
for (size_t i = 0; i < db.instrs.size(); i++) {
auto &instr = db.instrs[i];
if (instr.addr != sequenceAddr) {
sequenceAddr = instr.addr;
firstLineWithAddr = currentLine;
}
if (instr.contains(addr)) {
while (i < db.instrs.size() && db.instrs[i].addr == sequenceAddr) {
currentLine += db.instrs[i].text.lines.size();
i++;
}
QPointF topLeft = getInstructionOffset(db, static_cast<int>(firstLineWithAddr));
return QRectF(topLeft, QSizeF(block.width - 4 * charWidth,
charHeight * int(currentLine - firstLineWithAddr)));
}
currentLine += instr.text.lines.size();
}
return QRectF();
}
void DisassemblerGraphView::showInstruction(GraphView::GraphBlock &block, RVA addr)
{
QRectF rect = getInstrRect(block, addr);
rect.translate(block.x, block.y);
showRectangle(QRect(rect.x(), rect.y(), rect.width(), rect.height()), true);
}
DisassemblerGraphView::DisassemblyBlock *DisassemblerGraphView::blockForAddress(RVA addr)
2017-10-09 09:38:57 +00:00
{
2018-03-21 20:32:32 +00:00
for (auto &blockIt : disassembly_blocks) {
DisassemblyBlock &db = blockIt.second;
for (const Instr &i : db.instrs) {
2018-05-24 16:58:46 +00:00
if (i.addr == RVA_INVALID || i.size == RVA_INVALID) {
continue;
}
if (i.contains(addr)) {
return &db;
}
}
2017-10-09 09:38:57 +00:00
}
return nullptr;
2017-10-09 09:38:57 +00:00
}
const DisassemblerGraphView::Instr *DisassemblerGraphView::instrForAddress(RVA addr)
{
DisassemblyBlock *block = blockForAddress(addr);
for (const Instr &i : block->instrs) {
if (i.addr == RVA_INVALID || i.size == RVA_INVALID) {
continue;
}
if (i.contains(addr)) {
return &i;
}
}
return nullptr;
}
void DisassemblerGraphView::onSeekChanged(RVA addr)
2017-10-09 09:38:57 +00:00
{
blockMenu->setOffset(addr);
DisassemblyBlock *db = blockForAddress(addr);
bool switchFunction = false;
if (!db) {
// not in this function, try refreshing
refreshView();
db = blockForAddress(addr);
switchFunction = true;
}
if (db) {
// This is a local address! We animated to it.
transition_dont_seek = true;
showBlock(blocks[db->entry], !switchFunction);
showInstruction(blocks[db->entry], addr);
2017-10-09 09:38:57 +00:00
}
}
2017-12-13 22:57:36 +00:00
void DisassemblerGraphView::takeTrue()
2017-10-09 09:38:57 +00:00
{
DisassemblyBlock *db = blockForAddress(seekable->getOffset());
if (!db) {
return;
}
2018-03-21 20:32:32 +00:00
if (db->true_path != RVA_INVALID) {
seekable->seek(db->true_path);
} else if (!blocks[db->entry].edges.empty()) {
seekable->seek(blocks[db->entry].edges[0].target);
}
2017-10-09 09:38:57 +00:00
}
void DisassemblerGraphView::takeFalse()
2017-10-09 09:38:57 +00:00
{
DisassemblyBlock *db = blockForAddress(seekable->getOffset());
if (!db) {
return;
}
2018-03-21 20:32:32 +00:00
if (db->false_path != RVA_INVALID) {
seekable->seek(db->false_path);
} else if (!blocks[db->entry].edges.empty()) {
seekable->seek(blocks[db->entry].edges[0].target);
}
2017-10-09 09:38:57 +00:00
}
2017-12-14 21:07:48 +00:00
void DisassemblerGraphView::seekInstruction(bool previous_instr)
{
RVA addr = seekable->getOffset();
2017-12-14 21:07:48 +00:00
DisassemblyBlock *db = blockForAddress(addr);
2018-03-21 20:32:32 +00:00
if (!db) {
2017-12-14 21:07:48 +00:00
return;
}
2018-03-21 20:32:32 +00:00
for (size_t i = 0; i < db->instrs.size(); i++) {
2017-12-14 21:07:48 +00:00
Instr &instr = db->instrs[i];
if (!instr.contains(addr)) {
2017-12-14 21:07:48 +00:00
continue;
}
// Found the instruction. Check if a next one exists
2018-03-21 20:32:32 +00:00
if (!previous_instr && (i < db->instrs.size() - 1)) {
seekable->seek(db->instrs[i + 1].addr);
2018-03-21 20:32:32 +00:00
} else if (previous_instr && (i > 0)) {
while (i > 0 && db->instrs[i].addr == addr) { // jump over 0 size instructions
i--;
}
seekable->seek(db->instrs[i].addr);
break;
2017-12-14 21:07:48 +00:00
}
}
}
void DisassemblerGraphView::nextInstr()
{
seekInstruction(false);
}
void DisassemblerGraphView::prevInstr()
{
seekInstruction(true);
}
void DisassemblerGraphView::seekLocal(RVA addr, bool update_viewport)
2017-10-09 09:38:57 +00:00
{
RVA curAddr = seekable->getOffset();
if (addr == curAddr) {
return;
}
connectSeekChanged(true);
seekable->seek(addr);
connectSeekChanged(false);
2018-03-21 20:32:32 +00:00
if (update_viewport) {
2017-12-14 21:07:48 +00:00
viewport()->update();
}
2017-10-09 09:38:57 +00:00
}
void DisassemblerGraphView::copySelection()
{
if (!highlight_token) return;
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(highlight_token->content);
}
2018-09-30 20:00:53 +00:00
DisassemblerGraphView::Token *DisassemblerGraphView::getToken(Instr *instr, int x)
{
x -= (int) (3 * charWidth); // Ignore left margin
if (x < 0) {
return nullptr;
}
int clickedCharPos = mFontMetrics->position(instr->plainText, x);
if (clickedCharPos > instr->plainText.length()) {
return nullptr;
}
static const QRegularExpression tokenRegExp(R"(\b(?<!\.)([^\s]+)\b(?!\.))");
QRegularExpressionMatchIterator i = tokenRegExp.globalMatch(instr->plainText);
while (i.hasNext()) {
QRegularExpressionMatch match = i.next();
if (match.capturedStart() <= clickedCharPos && match.capturedEnd() > clickedCharPos) {
auto t = new Token;
t->start = match.capturedStart();
t->length = match.capturedLength();
t->content = match.captured();
t->instr = instr;
return t;
}
}
return nullptr;
}
QPoint DisassemblerGraphView::getInstructionOffset(const DisassemblyBlock &block, int line) const
{
return getTextOffset(line + static_cast<int>(block.header_text.lines.size()));
}
2018-03-21 20:32:32 +00:00
void DisassemblerGraphView::blockClicked(GraphView::GraphBlock &block, QMouseEvent *event,
QPoint pos)
2017-10-09 09:38:57 +00:00
{
Instr *instr = getInstrForMouseEvent(block, &pos, event->button() == Qt::RightButton);
if (!instr) {
2017-10-09 09:38:57 +00:00
return;
}
currentBlockAddress = block.entry;
highlight_token = getToken(instr, pos.x());
RVA addr = instr->addr;
seekLocal(addr);
2017-10-09 09:38:57 +00:00
blockMenu->setOffset(addr);
blockMenu->setCanCopy(highlight_token);
if (highlight_token) {
blockMenu->setCurHighlightedWord(highlight_token->content);
}
viewport()->update();
}
void DisassemblerGraphView::blockContextMenuRequested(GraphView::GraphBlock &block,
QContextMenuEvent *event, QPoint /*pos*/)
{
const RVA offset = this->seekable->getOffset();
actionUnhighlight.setVisible(Core()->getBBHighlighter()->getBasicBlock(block.entry));
actionUnhighlightInstruction.setVisible(Core()->getBIHighlighter()->getBasicInstruction(offset));
event->accept();
blockMenu->exec(event->globalPos());
}
void DisassemblerGraphView::contextMenuEvent(QContextMenuEvent *event)
{
GraphView::contextMenuEvent(event);
if (!event->isAccepted()) {
//TODO: handle opening block menu using keyboard
contextMenu->exec(event->globalPos());
event->accept();
2017-10-09 09:38:57 +00:00
}
}
void DisassemblerGraphView::showExportDialog()
{
QString defaultName = "graph";
if (auto f = Core()->functionIn(currentFcnAddr)) {
QString functionName = f->name;
// don't confuse image type guessing and make c++ names somewhat usable
functionName.replace(QRegularExpression("[.:]"), "_");
functionName.remove(QRegularExpression("[^a-zA-Z0-9_].*"));
if (!functionName.isEmpty()) {
defaultName = functionName;
}
}
showExportGraphDialog(defaultName, "agf", currentFcnAddr);
}
2018-03-21 20:32:32 +00:00
void DisassemblerGraphView::blockDoubleClicked(GraphView::GraphBlock &block, QMouseEvent *event,
QPoint pos)
2017-12-14 21:07:48 +00:00
{
Q_UNUSED(event);
seekable->seekToReference(getAddrForMouseEvent(block, &pos));
2017-12-14 21:07:48 +00:00
}
2018-03-21 20:32:32 +00:00
void DisassemblerGraphView::blockHelpEvent(GraphView::GraphBlock &block, QHelpEvent *event,
QPoint pos)
{
Instr *instr = getInstrForMouseEvent(block, &pos);
2018-03-21 20:32:32 +00:00
if (!instr || instr->fullText.lines.empty()) {
QToolTip::hideText();
event->ignore();
return;
}
QToolTip::showText(event->globalPos(), instr->fullText.ToQString());
}
bool DisassemblerGraphView::helpEvent(QHelpEvent *event)
{
2018-03-21 20:32:32 +00:00
if (!GraphView::helpEvent(event)) {
QToolTip::hideText();
event->ignore();
}
return true;
}
void DisassemblerGraphView::blockTransitionedTo(GraphView::GraphBlock *to)
2017-10-09 09:38:57 +00:00
{
currentBlockAddress = to->entry;
2018-03-21 20:32:32 +00:00
if (transition_dont_seek) {
transition_dont_seek = false;
return;
2017-10-09 09:38:57 +00:00
}
seekLocal(to->entry);
2017-10-16 19:00:47 +00:00
}
2018-02-14 09:33:34 +00:00
2019-09-19 05:19:50 +00:00
void DisassemblerGraphView::onActionHighlightBITriggered()
{
const RVA offset = this->seekable->getOffset();
const Instr *instr = instrForAddress(offset);
if (!instr) {
return;
}
auto bih = Core()->getBIHighlighter();
QColor background = ConfigColor("linehl");
if (auto currentColor = bih->getBasicInstruction(offset)) {
background = currentColor->color;
}
QColor c = QColorDialog::getColor(background, this, QString(),
QColorDialog::DontUseNativeDialog);
if (c.isValid()) {
bih->highlight(instr->addr, instr->size, c);
}
Config()->colorsUpdated();
}
void DisassemblerGraphView::onActionUnhighlightBITriggered()
{
const RVA offset = this->seekable->getOffset();
const Instr *instr = instrForAddress(offset);
if (!instr) {
return;
}
auto bih = Core()->getBIHighlighter();
bih->clear(instr->addr, instr->size);
Config()->colorsUpdated();
}
void DisassemblerGraphView::restoreCurrentBlock()
{
onSeekChanged(this->seekable->getOffset()); // try to keep the view on current block
}
void DisassemblerGraphView::paintEvent(QPaintEvent *event)
{
// DisassemblerGraphView is always dirty
setCacheDirty();
GraphView::paintEvent(event);
}
bool DisassemblerGraphView::Instr::contains(ut64 addr) const
{
return this->addr <= addr && (addr - this->addr) < size;
}