cutter/src/widgets/DisassemblerGraphView.cpp

970 lines
32 KiB
C++
Raw Normal View History

2017-10-09 09:38:57 +00:00
#include "DisassemblerGraphView.h"
#include "CutterSeekableWidget.h"
#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>
2018-02-14 09:33:34 +00:00
#include <QFileDialog>
#include <QFile>
#include <QVBoxLayout>
#include <QRegularExpression>
#include <QStandardPaths>
2017-10-09 09:38:57 +00:00
2018-03-04 17:42:02 +00:00
#include "Cutter.h"
2018-10-17 07:55:53 +00:00
#include "common/Colors.h"
#include "common/Configuration.h"
#include "common/CachedFontMetrics.h"
#include "common/TempConfig.h"
#include "common/SyntaxHighlighter.h"
2017-10-09 09:38:57 +00:00
DisassemblerGraphView::DisassemblerGraphView(QWidget *parent)
: GraphView(parent),
mFontMetrics(nullptr),
mMenu(new DisassemblyContextMenu(this)),
seekable(new CutterSeekableWidget(this))
2017-10-09 09:38:57 +00:00
{
2017-12-14 21:07:48 +00:00
highlight_token = nullptr;
// Signals that require a refresh all
connect(Core(), SIGNAL(refreshAll()), this, SLOT(refreshView()));
connect(Core(), SIGNAL(commentsChanged()), this, SLOT(refreshView()));
2018-03-21 20:32:32 +00:00
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()));
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
connect(Config(), SIGNAL(colorsUpdated()), this, SLOT(colorsUpdatedSlot()));
connect(Config(), SIGNAL(fontsUpdated()), this, SLOT(fontsUpdatedSlot()));
connectSeekChanged(false);
2017-10-09 09:38:57 +00:00
// Space to switch to disassembly
2017-12-14 21:07:48 +00:00
QShortcut *shortcut_disassembly = new QShortcut(QKeySequence(Qt::Key_Space), this);
shortcut_disassembly->setContext(Qt::WidgetShortcut);
2018-03-21 20:32:32 +00:00
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()));
2017-10-09 09:38:57 +00:00
2017-12-13 22:57:36 +00:00
// 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);
2017-12-13 22:57:36 +00:00
shortcut_zoom_out->setContext(Qt::WidgetShortcut);
connect(shortcut_zoom_out, SIGNAL(activated()), this, SLOT(zoomOut()));
2017-12-13 22:57:36 +00:00
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()));
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, 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()));
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, 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()));
2018-01-13 13:49:20 +00:00
QShortcut *shortcut_next_instr_arrow = new QShortcut(QKeySequence::MoveToNextLine, this);
shortcut_next_instr_arrow->setContext(Qt::WidgetShortcut);
connect(shortcut_next_instr_arrow, SIGNAL(activated()), this, SLOT(nextInstr()));
QShortcut *shortcut_prev_instr_arrow = new QShortcut(QKeySequence::MoveToPreviousLine, this);
shortcut_prev_instr_arrow->setContext(Qt::WidgetShortcut);
connect(shortcut_prev_instr_arrow, SIGNAL(activated()), this, SLOT(prevInstr()));
2017-12-14 21:07:48 +00:00
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);
2018-01-13 13:49:20 +00:00
shortcuts.append(shortcut_next_instr_arrow);
shortcuts.append(shortcut_prev_instr_arrow);
2017-12-14 21:07:48 +00:00
2018-02-14 09:33:34 +00:00
//Export Graph menu
mMenu->addSeparator();
actionExportGraph.setText(tr("Export Graph"));
mMenu->addAction(&actionExportGraph);
connect(&actionExportGraph, SIGNAL(triggered(bool)), this, SLOT(on_actionExportGraph_triggered()));
mMenu->addSeparator();
actionSyncOffset.setText(tr("Sync/unsync offset"));
mMenu->addAction(&actionSyncOffset);
connect(&actionSyncOffset, SIGNAL(triggered(bool)), this, SLOT(toggleSync()));
initFont();
colorsUpdatedSlot();
header = new QTextEdit(viewport());
header->setFixedHeight(30);
header->setReadOnly(true);
header->setLineWrapMode(QTextEdit::NoWrap);
highlighter = new SyntaxHighlighter(header->document());
2017-10-09 09:38:57 +00:00
}
void DisassemblerGraphView::connectSeekChanged(bool disconn)
{
if (disconn) {
2018-09-30 20:00:53 +00:00
disconnect(seekable, &CutterSeekableWidget::seekChanged, this,
&DisassemblerGraphView::onSeekChanged);
} else {
connect(seekable, &CutterSeekableWidget::seekChanged, 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::toggleSync()
{
seekable->toggleSyncWithCore();
if (seekable->getSyncWithCore()) {
parentWidget()->setWindowTitle(windowTitle);
} else {
parentWidget()->setWindowTitle(windowTitle + CutterSeekableWidget::tr(" (unsynced)"));
seekable->setIndependentOffset(Core()->getOffset());
}
}
void DisassemblerGraphView::refreshView()
2017-10-09 09:38:57 +00:00
{
initFont();
2017-10-09 09:38:57 +00:00
loadCurrentGraph();
2017-12-14 21:07:48 +00:00
viewport()->update();
2017-10-09 09:38:57 +00:00
}
void DisassemblerGraphView::loadCurrentGraph()
{
2018-02-01 14:15:16 +00:00
TempConfig tempConfig;
2018-02-01 14:51:03 +00:00
tempConfig.set("scr.html", true)
2018-03-21 20:32:32 +00:00
.set("scr.color", COLOR_MODE_16M)
.set("asm.bbline", false)
.set("asm.lines", false)
2018-05-24 06:21:12 +00:00
.set("asm.lines.fcn", false);
QJsonDocument functionsDoc = Core()->cmdj("agJ " + RAddressString(seekable->getOffset()));
2017-10-09 09:38:57 +00:00
QJsonArray functions = functionsDoc.array();
disassembly_blocks.clear();
2017-12-14 21:07:48 +00:00
blocks.clear();
if (highlight_token) {
delete highlight_token;
highlight_token = nullptr;
}
bool emptyGraph = functions.isEmpty();
if (emptyGraph) {
// If there's no function to print, just add a message
if (!emptyText) {
QVBoxLayout *layout = new QVBoxLayout(this);
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
Analysis anal;
anal.ready = true;
QJsonValue funcRef = functions.first();
QJsonObject func = funcRef.toObject();
Function f;
f.ready = true;
f.entry = func["offset"].toVariant().toULongLong();
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 + ")";
}
if (!seekable->getSyncWithCore()) {
parentWidget()->setWindowTitle(windowTitle + CutterSeekableWidget::tr(" (unsynced)"));
} else {
parentWidget()->setWindowTitle(windowTitle);
}
2017-10-09 09:38:57 +00:00
RVA entry = func["offset"].toVariant().toULongLong();
setEntry(entry);
2017-10-09 09:38:57 +00:00
for (QJsonValueRef blockRef : func["blocks"].toArray()) {
QJsonObject block = blockRef.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.exits.push_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.exits.push_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.exits.push_back(caseJump);
}
}
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-02-01 14:15:16 +00:00
// Skip last byte, otherwise it will overlap with next instruction
i.size -= 1;
2018-01-31 09:17:06 +00:00
2018-02-01 14:15:16 +00:00
QString disas;
disas = op["text"].toString();
2018-01-31 09:17:06 +00:00
2018-03-21 20:32:32 +00:00
QTextDocument textDoc;
textDoc.setHtml(disas);
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);
f.blocks.push_back(db);
addBlock(gb);
2017-10-09 09:38:57 +00:00
}
2017-10-09 09:38:57 +00:00
anal.functions[f.entry] = f;
anal.status = "Ready.";
anal.entry = f.entry;
2018-03-21 20:32:32 +00:00
if (func["blocks"].toArray().size() > 0) {
computeGraph(entry);
2017-12-14 21:07:48 +00:00
viewport()->update();
2018-03-21 20:32:32 +00:00
if (first_draw) {
2017-12-14 21:07:48 +00:00
showBlock(blocks[entry]);
first_draw = false;
}
}
}
2017-10-09 09:38:57 +00:00
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
}
}
2017-12-14 21:07:48 +00:00
int extra = 4 * charWidth + 4;
block.width = width + extra + charWidth;
block.height = (height * charHeight) + extra;
2017-10-09 09:38:57 +00:00
}
void DisassemblerGraphView::prepareHeader()
{
QString afcf = Core()->cmd("afcf").trimmed();
if (afcf.length() > 0) {
header->setPlainText(afcf);
header->show();
return;
}
QJsonArray func = Core()->cmdj("afij").array();
QJsonValue value = func.first();
QJsonObject obj = value.toObject();
QString name = obj["name"].toString().trimmed();
if (name.isEmpty()) {
header->hide();
return;
}
QString ret = obj["calltype"].toString().trimmed();
ret += (" ") + name + ("(");
QJsonArray args = obj["bpvars"].toArray();
QString argNames = QString();
for (QJsonValue value : args) {
QJsonObject obj = value.toObject();
QString kind = obj["kind"].toString();
if (kind == "arg") {
QString typeName = obj["type"].toString().trimmed();
QString argName = obj["name"].toString().trimmed();
if (argNames.isEmpty()) {
argNames += typeName + (" ") + argName;
} else {
argNames += (", ") + typeName + (" ") + argName;
}
}
}
ret += argNames + (")");
header->setPlainText(ret);
header->show();
}
void DisassemblerGraphView::initFont()
2017-11-05 15:08:17 +00:00
{
setFont(Config()->getFont());
2017-12-14 21:07:48 +00:00
QFontMetricsF metrics(font());
baseline = int(metrics.ascent());
charWidth = metrics.width('X');
charHeight = metrics.height();
charOffset = 0;
2018-03-21 20:32:32 +00:00
if (mFontMetrics)
delete mFontMetrics;
mFontMetrics = new CachedFontMetrics(this, font());
2017-11-05 15:08:17 +00:00
}
2018-03-21 20:32:32 +00:00
void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
2017-10-09 09:38:57 +00:00
{
p.setPen(Qt::black);
p.setBrush(Qt::gray);
p.drawRect(block.x, block.y, block.width, block.height);
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;
bool PCInBlock = 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) {
if ((instr.addr <= addr) && (addr <= instr.addr + instr.size)) {
block_selected = true;
selected_instruction = instr.addr;
}
if ((instr.addr <= PCAddr) && (PCAddr <= instr.addr + instr.size)) {
PCInBlock = true;
}
// 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));
}
// Node's shadow effect
p.drawRect(block.x + 2, block.y + 2,
block.width, block.height);
p.setPen(QPen(graphNodeColor, 1));
2018-03-21 20:32:32 +00:00
if (block_selected) {
p.setBrush(disassemblySelectedBackgroundColor);
} else {
p.setBrush(disassemblyBackgroundColor);
}
p.drawRect(block.x, block.y,
block.width, block.height);
// Draw different background for selected instruction
2018-03-21 20:32:32 +00:00
if (selected_instruction != RVA_INVALID) {
2017-12-14 21:07:48 +00:00
int y = block.y + (2 * charWidth) + (db.header_text.lines.size() * charHeight);
2018-03-21 20:32:32 +00:00
for (Instr &instr : db.instrs) {
if (instr.addr > selected_instruction) {
break;
}
auto selected = instr.addr == selected_instruction;
//auto traceCount = dbgfunctions->GetTraceRecordHitCount(instr.addr);
auto traceCount = 0;
2018-03-21 20:32:32 +00:00
if (selected && traceCount) {
2017-12-14 21:07:48 +00:00
p.fillRect(QRect(block.x + charWidth, y, block.width - (10 + 2 * charWidth),
int(instr.text.lines.size()) * charHeight), disassemblyTracedSelectionColor);
2018-03-21 20:32:32 +00:00
} else if (selected) {
2017-12-14 21:07:48 +00:00
p.fillRect(QRect(block.x + charWidth, y, block.width - (10 + 2 * charWidth),
int(instr.text.lines.size()) * charHeight), disassemblySelectionColor);
2018-03-21 20:32:32 +00:00
} else if (traceCount) {
// Color depending on how often a sequence of code is executed
int exponent = 1;
2018-03-21 20:32:32 +00:00
while (traceCount >>= 1) //log2(traceCount)
exponent++;
int colorDiff = (exponent * exponent) / 2;
// If the user has a light trace background color, substract
2018-03-21 20:32:32 +00:00
if (disassemblyTracedColor.blue() > 160)
colorDiff *= -1;
2018-03-21 20:32:32 +00:00
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))));
}
2017-12-14 21:07:48 +00:00
y += int(instr.text.lines.size()) * charHeight;
}
}
// highlight selected tokens
if (highlight_token != nullptr) {
int y = block.y + (2 * charWidth) + (db.header_text.lines.size() * charHeight);
int tokenWidth = mFontMetrics->width(highlight_token->content);
for (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;
}
int widthBefore = mFontMetrics->width(instr.plainText.left(pos));
2018-11-01 16:00:19 +00:00
if (charWidth * 3 + widthBefore > block.width - (10 + 2 * charWidth)) {
continue;
}
int highlightWidth = tokenWidth;
2018-11-01 16:00:19 +00:00
if (charWidth * 3 + widthBefore + tokenWidth >= block.width - (10 + 2 * charWidth)) {
highlightWidth = block.width - widthBefore - (10 + 4 * charWidth);
}
p.fillRect(QRect(block.x + charWidth * 3 + widthBefore, y, highlightWidth,
2018-09-30 20:00:53 +00:00
charHeight), disassemblySelectionColor.lighter(250));
}
y += int(instr.text.lines.size()) * charHeight;
}
}
// highlight program counter
if (PCInBlock) {
int y = block.y + (2 * charWidth) + (db.header_text.lines.size() * charHeight);
for (Instr &instr : db.instrs) {
if (instr.addr > PCAddr) {
break;
}
auto PC = instr.addr == PCAddr;
if (PC) {
p.fillRect(QRect(block.x + charWidth, y, block.width - (10 + 2 * charWidth),
int(instr.text.lines.size()) * charHeight), PCSelectionColor);
}
y += int(instr.text.lines.size()) * charHeight;
}
}
// Render node text
2017-12-14 21:07:48 +00:00
auto x = block.x + (2 * charWidth);
int y = block.y + (2 * charWidth);
2018-03-21 20:32:32 +00:00
for (auto &line : db.header_text.lines) {
2017-12-14 21:07:48 +00:00
RichTextPainter::paintRichText(&p, x, y, block.width, charHeight, 0, line, mFontMetrics);
y += charHeight;
}
2018-03-21 20:32:32 +00:00
for (Instr &instr : db.instrs) {
if (Core()->isBreakpoint(breakpoints, instr.addr)) {
p.fillRect(QRect(block.x + charWidth, y, block.width - (10 + 2 * charWidth),
int(instr.text.lines.size()) * charHeight), ConfigColor("gui.breakpoint_background"));
if (instr.addr == selected_instruction) {
p.fillRect(QRect(block.x + charWidth, y, block.width - (10 + 2 * charWidth),
int(instr.text.lines.size()) * charHeight), 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
2017-12-14 21:07:48 +00:00
QRectF bpRect(x - rectSize / 3.0, y + (charHeight - rectSize) / 2.0, rectSize, rectSize);
// TODO: Breakpoint/Cip stuff
2018-03-21 20:32:32 +00:00
RichTextPainter::paintRichText(&p, x + charWidth, y, block.width - charWidth, charHeight, 0, line,
mFontMetrics);
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,
GraphView::GraphBlock *to)
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;
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 = (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();
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)
{
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();
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();
}
2017-10-09 09:38:57 +00:00
return nullptr;
}
// Public Slots
2017-10-09 09:38:57 +00:00
void DisassemblerGraphView::colorsUpdatedSlot()
{
2017-11-20 11:23:37 +00:00
disassemblyBackgroundColor = ConfigColor("gui.alt_background");
2018-05-02 06:02:24 +00:00
disassemblySelectedBackgroundColor = ConfigColor("gui.disass_selected");
2017-10-22 10:21:44 +00:00
mDisabledBreakpointColor = disassemblyBackgroundColor;
2017-10-15 07:14:05 +00:00
graphNodeColor = ConfigColor("gui.border");
2017-11-20 11:23:37 +00:00
backgroundColor = ConfigColor("gui.background");
2017-12-02 15:22:05 +00:00
disassemblySelectionColor = ConfigColor("highlight");
PCSelectionColor = ConfigColor("highlightPC");
2017-10-15 07:14:05 +00:00
jmpColor = ConfigColor("graph.trufae");
brtrueColor = ConfigColor("graph.true");
brfalseColor = ConfigColor("graph.false");
2017-11-05 15:08:17 +00:00
mCommentColor = ConfigColor("comment");
initFont();
2017-11-21 14:33:15 +00:00
refreshView();
2017-10-09 09:38:57 +00:00
}
void DisassemblerGraphView::fontsUpdatedSlot()
{
2017-12-14 21:07:48 +00:00
initFont();
refreshView();
2017-10-09 09:38:57 +00:00
}
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;
2018-03-21 20:32:32 +00:00
for (Instr i : db.instrs) {
2018-05-24 16:58:46 +00:00
if (i.addr == RVA_INVALID || i.size == RVA_INVALID) {
continue;
}
2018-03-21 20:32:32 +00:00
if ((i.addr <= addr) && (addr <= i.addr + i.size)) {
return &db;
}
}
2017-10-09 09:38:57 +00:00
}
return nullptr;
2017-10-09 09:38:57 +00:00
}
void DisassemblerGraphView::onSeekChanged(RVA addr)
2017-10-09 09:38:57 +00:00
{
mMenu->setOffset(addr);
DisassemblyBlock *db = blockForAddress(addr);
if (db) {
// This is a local address! We animated to it.
transition_dont_seek = true;
showBlock(&blocks[db->entry], true);
prepareHeader();
return;
} else {
refreshView();
db = blockForAddress(addr);
2018-03-21 20:32:32 +00:00
if (db) {
// This is a local address! We animated to it.
transition_dont_seek = true;
showBlock(&blocks[db->entry], true);
prepareHeader();
return;
}
2017-10-09 09:38:57 +00:00
}
}
void DisassemblerGraphView::zoomIn(QPoint mouse)
2017-10-09 09:38:57 +00:00
{
current_scale += 0.1;
2017-12-14 21:07:48 +00:00
auto areaSize = viewport()->size();
adjustSize(areaSize.width(), areaSize.height(), mouse);
2017-12-14 21:07:48 +00:00
viewport()->update();
2017-10-09 09:38:57 +00:00
}
void DisassemblerGraphView::zoomOut(QPoint mouse)
2017-10-09 09:38:57 +00:00
{
current_scale -= 0.1;
current_scale = std::max(current_scale, 0.3);
2017-12-14 21:07:48 +00:00
auto areaSize = viewport()->size();
adjustSize(areaSize.width(), areaSize.height(), mouse);
2017-12-14 21:07:48 +00:00
viewport()->update();
2017-10-09 09:38:57 +00:00
}
2017-12-13 22:57:36 +00:00
void DisassemblerGraphView::zoomReset()
{
current_scale = 1.0;
2017-12-14 21:07:48 +00:00
auto areaSize = viewport()->size();
adjustSize(areaSize.width(), areaSize.height());
viewport()->update();
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);
2018-03-21 20:32:32 +00:00
} else if (blocks[db->entry].exits.size()) {
seekable->seek(blocks[db->entry].exits[0]);
}
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);
2018-03-21 20:32:32 +00:00
} else if (blocks[db->entry].exits.size()) {
seekable->seek(blocks[db->entry].exits[0]);
}
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];
2018-03-21 20:32:32 +00:00
if (!((instr.addr <= addr) && (addr <= instr.addr + instr.size))) {
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)) {
seekable->seek(db->instrs[i - 1].addr);
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
{
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::seekPrev()
2017-10-09 09:38:57 +00:00
{
if (seekable->getSyncWithCore()) {
Core()->seekPrev();
} else {
seekable->seek(seekable->getPrevIndependentOffset());
}
2017-10-09 09:38:57 +00:00
}
2018-09-30 20:00:53 +00:00
DisassemblerGraphView::Token *DisassemblerGraphView::getToken(Instr *instr, int x)
{
x -= (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("\\b(?<!\\.)([^\\s]+)\\b(?!\\.)");
QRegularExpressionMatchIterator i = tokenRegExp.globalMatch(instr->plainText);
while (i.hasNext()) {
QRegularExpressionMatch match = i.next();
if (match.capturedStart() <= clickedCharPos && match.capturedEnd() > clickedCharPos) {
2018-09-30 20:00:53 +00:00
Token *t = new Token;
t->start = match.capturedStart();
t->length = match.capturedLength();
t->content = match.captured();
t->instr = instr;
return t;
}
}
return nullptr;
}
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);
if (!instr) {
2017-10-09 09:38:57 +00:00
return;
}
highlight_token = getToken(instr, pos.x());
RVA addr = instr->addr;
seekLocal(addr);
2017-10-09 09:38:57 +00:00
mMenu->setOffset(addr);
2018-03-21 20:32:32 +00:00
if (event->button() == Qt::RightButton) {
mMenu->exec(event->globalPos());
2017-10-09 09:38:57 +00:00
}
}
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);
RVA instr = getAddrForMouseEvent(block, &pos);
2018-03-21 20:32:32 +00:00
if (instr == RVA_INVALID) {
2017-12-14 21:07:48 +00:00
return;
}
QList<XrefDescription> refs = Core()->getXRefs(instr, false, false);
if (refs.length()) {
seekable->seek(refs.at(0).to);
2017-12-14 21:07:48 +00:00
}
if (refs.length() > 1) {
qWarning() << "Too many references here. Weird behaviour expected.";
}
}
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
{
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
2018-02-14 09:33:34 +00:00
void DisassemblerGraphView::on_actionExportGraph_triggered()
{
QStringList filters;
filters.append(tr("Graphiz dot (*.dot)"));
if (!QStandardPaths::findExecutable("dot").isEmpty()
|| !QStandardPaths::findExecutable("xdot").isEmpty()) {
filters.append(tr("GIF (*.gif)"));
filters.append(tr("PNG (*.png)"));
filters.append(tr("JPEG (*.jpg)"));
filters.append(tr("PostScript (*.ps)"));
filters.append(tr("SVG (*.svg)"));
filters.append(tr("JSON (*.json)"));
}
QFileDialog dialog(this, tr("Export Graph"));
dialog.setAcceptMode(QFileDialog::AcceptSave);
dialog.setFileMode(QFileDialog::AnyFile);
dialog.setNameFilters(filters);
dialog.selectFile("graph");
dialog.setDefaultSuffix("dot");
if (!dialog.exec())
return;
int startIdx = dialog.selectedNameFilter().lastIndexOf("*.") + 2;
int count = dialog.selectedNameFilter().length() - startIdx - 1;
QString format = dialog.selectedNameFilter().mid(startIdx, count);
QString fileName = dialog.selectedFiles()[0];
if (format != "dot") {
TempConfig tempConfig;
tempConfig.set("graph.gv.format", format);
qWarning() << Core()->cmd(QString("agfw \"%1\" @ $FB").arg(fileName));
return;
}
2018-02-14 09:33:34 +00:00
QFile file(fileName);
2018-03-21 20:32:32 +00:00
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
2018-02-14 09:33:34 +00:00
qWarning() << "Can't open file";
return;
}
QTextStream fileOut(&file);
fileOut << Core()->cmd("agfd $FB");
2018-02-14 09:33:34 +00:00
}
void DisassemblerGraphView::wheelEvent(QWheelEvent *event)
{
// when CTRL is pressed, we zoom in/out with mouse wheel
if (Qt::ControlModifier == event->modifiers()) {
const QPoint numDegrees = event->angleDelta() / 8;
if (!numDegrees.isNull()) {
const QPoint numSteps = numDegrees / 15;
QPoint mouse = event->globalPos();
if (numSteps.y() > 0) {
zoomIn(mouse);
} else if (numSteps.y() < 0) {
zoomOut(mouse);
}
}
event->accept();
} else {
// use mouse wheel for scrolling when CTRL is not pressed
GraphView::wheelEvent(event);
}
}