mirror of
https://github.com/rizinorg/cutter.git
synced 2024-12-23 05:16:11 +00:00
734 lines
22 KiB
C++
734 lines
22 KiB
C++
#include "DisassemblerGraphView.h"
|
|
|
|
#include <QPainter>
|
|
#include <QJsonObject>
|
|
#include <QJsonArray>
|
|
#include <QMouseEvent>
|
|
#include <QPropertyAnimation>
|
|
#include <QShortcut>
|
|
#include <QToolTip>
|
|
#include <QTextDocument>
|
|
#include <QFileDialog>
|
|
#include <QFile>
|
|
|
|
#include "cutter.h"
|
|
#include "utils/Colors.h"
|
|
#include "utils/Configuration.h"
|
|
#include "utils/CachedFontMetrics.h"
|
|
#include "utils/TempConfig.h"
|
|
|
|
DisassemblerGraphView::DisassemblerGraphView(QWidget *parent)
|
|
: GraphView(parent),
|
|
mFontMetrics(nullptr),
|
|
mMenu(new DisassemblyContextMenu(this))
|
|
{
|
|
highlight_token = nullptr;
|
|
// Signals that require a refresh all
|
|
connect(Core(), SIGNAL(refreshAll()), this, SLOT(refreshView()));
|
|
connect(Core(), SIGNAL(commentsChanged()), this, SLOT(refreshView()));
|
|
connect(Core(), SIGNAL(functionRenamed(const QString &, const QString &)), this, SLOT(refreshView()));
|
|
connect(Core(), SIGNAL(flagsChanged()), this, SLOT(refreshView()));
|
|
connect(Core(), SIGNAL(varsChanged()), this, SLOT(refreshView()));
|
|
connect(Core(), SIGNAL(instructionChanged(RVA)), this, SLOT(refreshView()));
|
|
connect(Core(), SIGNAL(functionsChanged()), this, SLOT(refreshView()));
|
|
connect(Core(), SIGNAL(graphOptionsChanged()), this, SLOT(refreshView()));
|
|
connect(Core(), SIGNAL(asmOptionsChanged()), this, SLOT(refreshView()));
|
|
|
|
connect(Config(), SIGNAL(colorsUpdated()), this, SLOT(colorsUpdatedSlot()));
|
|
connect(Config(), SIGNAL(fontsUpdated()), this, SLOT(fontsUpdatedSlot()));
|
|
connect(Core(), SIGNAL(seekChanged(RVA)), this, SLOT(onSeekChanged(RVA)));
|
|
|
|
// Space to switch to disassembly
|
|
QShortcut *shortcut_disassembly = new QShortcut(QKeySequence(Qt::Key_Space), this);
|
|
shortcut_disassembly->setContext(Qt::WidgetShortcut);
|
|
connect(shortcut_disassembly, &QShortcut::activated, this, []{
|
|
Core()->setMemoryWidgetPriority(CutterCore::MemoryWidgetType::Disassembly);
|
|
Core()->triggerRaisePrioritizedMemoryWidget();
|
|
});
|
|
// ESC for previous
|
|
QShortcut *shortcut_escape = new QShortcut(QKeySequence(Qt::Key_Escape), this);
|
|
shortcut_escape->setContext(Qt::WidgetShortcut);
|
|
connect(shortcut_escape, SIGNAL(activated()), this, SLOT(seekPrev()));
|
|
|
|
// Zoom shortcuts
|
|
QShortcut *shortcut_zoom_in = new QShortcut(QKeySequence(Qt::Key_Plus), this);
|
|
shortcut_zoom_in->setContext(Qt::WidgetShortcut);
|
|
connect(shortcut_zoom_in, SIGNAL(activated()), this, SLOT(zoomIn()));
|
|
QShortcut *shortcut_zoom_out = new QShortcut(QKeySequence(Qt::Key_Minus), this);
|
|
shortcut_zoom_out->setContext(Qt::WidgetShortcut);
|
|
connect(shortcut_zoom_out, SIGNAL(activated()), this, SLOT(zoomOut()));
|
|
QShortcut *shortcut_zoom_reset = new QShortcut(QKeySequence(Qt::Key_Equal), this);
|
|
shortcut_zoom_reset->setContext(Qt::WidgetShortcut);
|
|
connect(shortcut_zoom_reset, SIGNAL(activated()), this, SLOT(zoomReset()));
|
|
|
|
// Branch shortcuts
|
|
QShortcut *shortcut_take_true = new QShortcut(QKeySequence(Qt::Key_T), this);
|
|
shortcut_take_true->setContext(Qt::WidgetShortcut);
|
|
connect(shortcut_take_true, SIGNAL(activated()), this, SLOT(takeTrue()));
|
|
QShortcut *shortcut_take_false = new QShortcut(QKeySequence(Qt::Key_F), this);
|
|
shortcut_take_false->setContext(Qt::WidgetShortcut);
|
|
connect(shortcut_take_false, SIGNAL(activated()), this, SLOT(takeFalse()));
|
|
|
|
// Navigation shortcuts
|
|
QShortcut *shortcut_next_instr = new QShortcut(QKeySequence(Qt::Key_J), this);
|
|
shortcut_next_instr->setContext(Qt::WidgetShortcut);
|
|
connect(shortcut_next_instr, SIGNAL(activated()), this, SLOT(nextInstr()));
|
|
QShortcut *shortcut_prev_instr = new QShortcut(QKeySequence(Qt::Key_K), this);
|
|
shortcut_prev_instr->setContext(Qt::WidgetShortcut);
|
|
connect(shortcut_prev_instr, SIGNAL(activated()), this, SLOT(prevInstr()));
|
|
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()));
|
|
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);
|
|
shortcuts.append(shortcut_next_instr_arrow);
|
|
shortcuts.append(shortcut_prev_instr_arrow);
|
|
|
|
//Export Graph menu
|
|
mMenu->addSeparator();
|
|
actionExportGraph.setText(tr("Export Graph"));
|
|
mMenu->addAction(&actionExportGraph);
|
|
connect(&actionExportGraph, SIGNAL(triggered(bool)), this, SLOT(on_actionExportGraph_triggered()));
|
|
|
|
initFont();
|
|
colorsUpdatedSlot();
|
|
}
|
|
|
|
DisassemblerGraphView::~DisassemblerGraphView()
|
|
{
|
|
for(QShortcut *shortcut : shortcuts)
|
|
{
|
|
delete shortcut;
|
|
}
|
|
}
|
|
|
|
void DisassemblerGraphView::refreshView()
|
|
{
|
|
initFont();
|
|
loadCurrentGraph();
|
|
viewport()->update();
|
|
}
|
|
|
|
void DisassemblerGraphView::loadCurrentGraph()
|
|
{
|
|
TempConfig tempConfig;
|
|
tempConfig.set("scr.html", true)
|
|
.set("scr.color", COLOR_MODE_16M)
|
|
.set("asm.bbline", false)
|
|
.set("asm.lines", false)
|
|
.set("asm.fcnlines", false);
|
|
QJsonDocument functionsDoc = Core()->cmdj("agJ");
|
|
QJsonArray functions = functionsDoc.array();
|
|
|
|
disassembly_blocks.clear();
|
|
blocks.clear();
|
|
|
|
Analysis anal;
|
|
anal.ready = true;
|
|
|
|
QJsonValue funcRef = functions.first();
|
|
QJsonObject func = funcRef.toObject();
|
|
Function f;
|
|
f.ready = true;
|
|
f.entry = func["offset"].toVariant().toULongLong();
|
|
|
|
QString windowTitle = tr("Graph");
|
|
QString funcName = func["name"].toString().trimmed();
|
|
if (!funcName.isEmpty())
|
|
{
|
|
windowTitle += " (" + funcName + ")";
|
|
}
|
|
parentWidget()->setWindowTitle(windowTitle);
|
|
|
|
RVA entry = func["offset"].toVariant().toULongLong();
|
|
|
|
setEntry(entry);
|
|
for (QJsonValueRef blockRef : func["blocks"].toArray()) {
|
|
QJsonObject block = blockRef.toObject();
|
|
RVA block_entry = block["offset"].toVariant().toULongLong();
|
|
RVA block_size = block["size"].toVariant().toULongLong();
|
|
RVA block_fail = block["fail"].toVariant().toULongLong();
|
|
RVA block_jump = block["jump"].toVariant().toULongLong();
|
|
|
|
DisassemblyBlock db;
|
|
GraphBlock gb;
|
|
gb.entry = block_entry;
|
|
db.entry = block_entry;
|
|
db.true_path = RVA_INVALID;
|
|
db.false_path = RVA_INVALID;
|
|
if(block_fail)
|
|
{
|
|
db.false_path = block_fail;
|
|
gb.exits.push_back(block_fail);
|
|
}
|
|
if(block_jump)
|
|
{
|
|
if(block_fail)
|
|
{
|
|
db.true_path = block_jump;
|
|
}
|
|
gb.exits.push_back(block_jump);
|
|
}
|
|
QJsonArray opArray = block["ops"].toArray();
|
|
for (int opIndex=0; opIndex<opArray.size(); opIndex++)
|
|
{
|
|
QJsonObject op = opArray[opIndex].toObject();
|
|
Instr i;
|
|
i.addr = op["offset"].toVariant().toULongLong();
|
|
|
|
if (opIndex < opArray.size() - 1)
|
|
{
|
|
// get instruction size from distance to next instruction ...
|
|
RVA nextOffset = opArray[opIndex+1].toObject()["offset"].toVariant().toULongLong();
|
|
i.size = nextOffset - i.addr;
|
|
}
|
|
else
|
|
{
|
|
// or to the end of the block.
|
|
i.size = (block_entry + block_size) - i.addr;
|
|
}
|
|
|
|
// Skip last byte, otherwise it will overlap with next instruction
|
|
i.size -= 1;
|
|
|
|
QString disas;
|
|
disas = op["text"].toString();
|
|
|
|
QTextDocument textDoc;
|
|
textDoc.setHtml(disas);
|
|
|
|
RichTextPainter::List richText = RichTextPainter::fromTextDocument(textDoc);
|
|
//Colors::colorizeAssembly(richText, textDoc.toPlainText(), 0);
|
|
|
|
bool cropped;
|
|
int blockLength = Config()->getGraphBlockMaxChars() + Core()->getConfigb("asm.bytes") * 24 + Core()->getConfigb("asm.emu") * 10;
|
|
i.text = Text(RichTextPainter::cropped(richText, blockLength, "...", &cropped));
|
|
if(cropped)
|
|
i.fullText = richText;
|
|
else
|
|
i.fullText = Text();
|
|
db.instrs.push_back(i);
|
|
}
|
|
disassembly_blocks[db.entry] = db;
|
|
prepareGraphNode(gb);
|
|
f.blocks.push_back(db);
|
|
|
|
addBlock(gb);
|
|
}
|
|
|
|
anal.functions[f.entry] = f;
|
|
anal.status = "Ready.";
|
|
anal.entry = f.entry;
|
|
|
|
if(func["blocks"].toArray().size() > 0)
|
|
{
|
|
computeGraph(entry);
|
|
viewport()->update();
|
|
|
|
if(first_draw)
|
|
{
|
|
showBlock(blocks[entry]);
|
|
first_draw = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DisassemblerGraphView::prepareGraphNode(GraphBlock &block)
|
|
{
|
|
DisassemblyBlock &db = disassembly_blocks[block.entry];
|
|
int width = 0;
|
|
int height = 0;
|
|
for(auto & line : db.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 : db.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 * charWidth + 4;
|
|
block.width = width + extra + charWidth;
|
|
block.height = (height * charHeight) + extra;
|
|
}
|
|
|
|
|
|
void DisassemblerGraphView::initFont()
|
|
{
|
|
setFont(Config()->getFont());
|
|
QFontMetricsF metrics(font());
|
|
baseline = int(metrics.ascent());
|
|
charWidth = metrics.width('X');
|
|
charHeight = metrics.height();
|
|
charOffset = 0;
|
|
if(mFontMetrics)
|
|
delete mFontMetrics;
|
|
mFontMetrics = new CachedFontMetrics(this, font());
|
|
}
|
|
|
|
void DisassemblerGraphView::drawBlock(QPainter & p, GraphView::GraphBlock &block)
|
|
{
|
|
p.setPen(Qt::black);
|
|
p.setBrush(Qt::gray);
|
|
p.drawRect(block.x, block.y, block.width, block.height);
|
|
|
|
|
|
// Render node
|
|
DisassemblyBlock &db = disassembly_blocks[block.entry];
|
|
bool block_selected = false;
|
|
RVA selected_instruction = RVA_INVALID;
|
|
|
|
// Figure out if the current block is selected
|
|
for(const Instr & instr : db.instrs)
|
|
{
|
|
RVA addr = Core()->getOffset();
|
|
if((instr.addr <= addr) && (addr <= instr.addr+instr.size))
|
|
{
|
|
block_selected = true;
|
|
selected_instruction = instr.addr;
|
|
}
|
|
// TODO: L219
|
|
}
|
|
|
|
p.setPen(QColor(0, 0, 0, 0));
|
|
if(db.terminal)
|
|
{
|
|
p.setBrush(retShadowColor);
|
|
} else if(db.indirectcall) {
|
|
p.setBrush(indirectcallShadowColor);
|
|
} else {
|
|
p.setBrush(QColor(0, 0, 0, 128));
|
|
}
|
|
|
|
p.drawRect(block.x + 4, block.y + 4,
|
|
block.width + 4, block.height + 4);
|
|
p.setPen(graphNodeColor);
|
|
|
|
if(block_selected)
|
|
{
|
|
p.setBrush(disassemblySelectedBackgroundColor);
|
|
} else {
|
|
p.setBrush(disassemblyBackgroundColor);
|
|
}
|
|
|
|
p.drawRect(block.x, block.y,
|
|
block.width, block.height);
|
|
|
|
// Draw different background for selected instruction
|
|
if(selected_instruction != RVA_INVALID)
|
|
{
|
|
int y = block.y + (2 * charWidth) + (db.header_text.lines.size() * charHeight);
|
|
for(Instr & instr : db.instrs)
|
|
{
|
|
auto selected = instr.addr == selected_instruction;
|
|
//auto traceCount = dbgfunctions->GetTraceRecordHitCount(instr.addr);
|
|
auto traceCount = 0;
|
|
if(selected && traceCount)
|
|
{
|
|
p.fillRect(QRect(block.x + charWidth, y, block.width - (10 + 2 * charWidth),
|
|
int(instr.text.lines.size()) * charHeight), disassemblyTracedSelectionColor);
|
|
}
|
|
else if(selected)
|
|
{
|
|
p.fillRect(QRect(block.x + charWidth, y, block.width - (10 + 2 * charWidth),
|
|
int(instr.text.lines.size()) * 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 + charWidth, y, block.width - (10 + 2 * charWidth), int(instr.text.lines.size()) * charHeight),
|
|
QColor(disassemblyTracedColor.red(),
|
|
disassemblyTracedColor.green(),
|
|
std::max(0, std::min(256, disassemblyTracedColor.blue() + colorDiff))));
|
|
}
|
|
y += int(instr.text.lines.size()) * charHeight;
|
|
}
|
|
}
|
|
|
|
|
|
// Render node text
|
|
auto x = block.x + (2 * charWidth);
|
|
int y = block.y + (2 * charWidth);
|
|
for(auto & line : db.header_text.lines)
|
|
{
|
|
RichTextPainter::paintRichText(&p, x, y, block.width, charHeight, 0, line, mFontMetrics);
|
|
y += charHeight;
|
|
}
|
|
for(Instr & instr : db.instrs)
|
|
{
|
|
for(auto & line : instr.text.lines)
|
|
{
|
|
int rectSize = qRound(charWidth);
|
|
if(rectSize % 2)
|
|
{
|
|
rectSize++;
|
|
}
|
|
// Assume charWidth <= charHeight
|
|
QRectF bpRect(x - rectSize / 3.0, y + (charHeight - rectSize) / 2.0, rectSize, rectSize);
|
|
|
|
// TODO: Breakpoint/Cip stuff
|
|
|
|
RichTextPainter::paintRichText(&p, x + charWidth, y, block.width - charWidth, charHeight, 0, line, mFontMetrics);
|
|
y += charHeight;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
GraphView::EdgeConfiguration DisassemblerGraphView::edgeConfiguration(GraphView::GraphBlock &from, GraphView::GraphBlock *to)
|
|
{
|
|
EdgeConfiguration ec;
|
|
DisassemblyBlock &db = disassembly_blocks[from.entry];
|
|
if(to->entry == db.true_path)
|
|
{
|
|
ec.color = brtrueColor;
|
|
}
|
|
else if(to->entry == db.false_path)
|
|
{
|
|
ec.color = brfalseColor;
|
|
}
|
|
else
|
|
{
|
|
ec.color = jmpColor;
|
|
}
|
|
ec.start_arrow = false;
|
|
ec.end_arrow = true;
|
|
return ec;
|
|
}
|
|
|
|
RVA DisassemblerGraphView::getAddrForMouseEvent(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();
|
|
if (mouse_row < cur_row)
|
|
{
|
|
return db.entry;
|
|
}
|
|
|
|
Instr *instr = getInstrForMouseEvent(block, point);
|
|
if(instr)
|
|
{
|
|
return instr->addr;
|
|
}
|
|
|
|
return RVA_INVALID;
|
|
}
|
|
|
|
|
|
DisassemblerGraphView::Instr *DisassemblerGraphView::getInstrForMouseEvent(GraphView::GraphBlock &block, QPoint *point)
|
|
{
|
|
DisassemblyBlock &db = disassembly_blocks[block.entry];
|
|
|
|
// Remove header and margin
|
|
int off_y = (2 * charWidth) + (db.header_text.lines.size() * charHeight);
|
|
// Get mouse coordinate over the actual text
|
|
int text_point_y = point->y() - off_y;
|
|
int mouse_row = text_point_y / charHeight;
|
|
|
|
int cur_row = db.header_text.lines.size();
|
|
|
|
for(Instr & instr : db.instrs)
|
|
{
|
|
if(mouse_row < cur_row + (int)instr.text.lines.size())
|
|
{
|
|
return &instr;
|
|
}
|
|
cur_row += instr.text.lines.size();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// Public Slots
|
|
|
|
void DisassemblerGraphView::colorsUpdatedSlot()
|
|
{
|
|
disassemblyBackgroundColor = ConfigColor("gui.alt_background");
|
|
disassemblySelectedBackgroundColor = ConfigColor("gui.background");
|
|
mDisabledBreakpointColor = disassemblyBackgroundColor;
|
|
graphNodeColor = ConfigColor("gui.border");
|
|
backgroundColor = ConfigColor("gui.background");
|
|
disassemblySelectionColor = ConfigColor("highlight");
|
|
|
|
jmpColor = ConfigColor("graph.trufae");
|
|
brtrueColor = ConfigColor("graph.true");
|
|
brfalseColor = ConfigColor("graph.false");
|
|
|
|
mCommentColor = ConfigColor("comment");
|
|
initFont();
|
|
refreshView();
|
|
}
|
|
|
|
void DisassemblerGraphView::fontsUpdatedSlot()
|
|
{
|
|
initFont();
|
|
refreshView();
|
|
}
|
|
|
|
DisassemblerGraphView::DisassemblyBlock *DisassemblerGraphView::blockForAddress(RVA addr)
|
|
{
|
|
for(auto & blockIt : disassembly_blocks)
|
|
{
|
|
DisassemblyBlock &db = blockIt.second;
|
|
for(Instr i : db.instrs)
|
|
{
|
|
if((i.addr <= addr) && (addr <= i.addr + i.size))
|
|
{
|
|
return &db;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void DisassemblerGraphView::onSeekChanged(RVA addr)
|
|
{
|
|
mMenu->setOffset(addr);
|
|
// If this seek was NOT done by us...
|
|
if(!sent_seek)
|
|
{
|
|
DisassemblyBlock *db = blockForAddress(addr);
|
|
if(db)
|
|
{
|
|
// This is a local address! We animated to it.
|
|
transition_dont_seek = true;
|
|
showBlock(&blocks[db->entry], true);
|
|
return;
|
|
} else {
|
|
refreshView();
|
|
DisassemblyBlock *db = blockForAddress(addr);
|
|
if(db)
|
|
{
|
|
// This is a local address! We animated to it.
|
|
transition_dont_seek = true;
|
|
showBlock(&blocks[db->entry], false);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
sent_seek = false;
|
|
}
|
|
|
|
void DisassemblerGraphView::zoomIn()
|
|
{
|
|
current_scale += 0.1;
|
|
auto areaSize = viewport()->size();
|
|
adjustSize(areaSize.width(), areaSize.height());
|
|
viewport()->update();
|
|
}
|
|
|
|
void DisassemblerGraphView::zoomOut()
|
|
{
|
|
current_scale -= 0.1;
|
|
current_scale = std::max(current_scale, 0.3);
|
|
auto areaSize = viewport()->size();
|
|
adjustSize(areaSize.width(), areaSize.height());
|
|
viewport()->update();
|
|
}
|
|
|
|
void DisassemblerGraphView::zoomReset()
|
|
{
|
|
current_scale = 1.0;
|
|
auto areaSize = viewport()->size();
|
|
adjustSize(areaSize.width(), areaSize.height());
|
|
viewport()->update();
|
|
}
|
|
|
|
void DisassemblerGraphView::takeTrue()
|
|
{
|
|
DisassemblyBlock *db = blockForAddress(Core()->getOffset());
|
|
if(db->true_path != RVA_INVALID)
|
|
{
|
|
Core()->seek(db->true_path);
|
|
}
|
|
else if(blocks[db->entry].exits.size())
|
|
{
|
|
Core()->seek(blocks[db->entry].exits[0]);
|
|
}
|
|
}
|
|
|
|
void DisassemblerGraphView::takeFalse()
|
|
{
|
|
DisassemblyBlock *db = blockForAddress(Core()->getOffset());
|
|
if(db->false_path != RVA_INVALID)
|
|
{
|
|
Core()->seek(db->false_path);
|
|
}
|
|
else if(blocks[db->entry].exits.size())
|
|
{
|
|
Core()->seek(blocks[db->entry].exits[0]);
|
|
}
|
|
}
|
|
|
|
void DisassemblerGraphView::seekInstruction(bool previous_instr)
|
|
{
|
|
RVA addr = Core()->getOffset();
|
|
DisassemblyBlock *db = blockForAddress(addr);
|
|
if(!db)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for(size_t i=0; i < db->instrs.size(); i++)
|
|
{
|
|
Instr &instr = db->instrs[i];
|
|
if(!((instr.addr <= addr) && (addr <= instr.addr + instr.size)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Found the instructon. Check if a next one exists
|
|
if(!previous_instr && (i < db->instrs.size()-1))
|
|
{
|
|
seek(db->instrs[i+1].addr, true);
|
|
}
|
|
else if(previous_instr && (i > 0))
|
|
{
|
|
seek(db->instrs[i-1].addr);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DisassemblerGraphView::nextInstr()
|
|
{
|
|
seekInstruction(false);
|
|
}
|
|
|
|
void DisassemblerGraphView::prevInstr()
|
|
{
|
|
seekInstruction(true);
|
|
}
|
|
|
|
void DisassemblerGraphView::seek(RVA addr, bool update_viewport)
|
|
{
|
|
sent_seek = true;
|
|
Core()->seek(addr);
|
|
if(update_viewport)
|
|
{
|
|
viewport()->update();
|
|
}
|
|
}
|
|
|
|
void DisassemblerGraphView::seekPrev()
|
|
{
|
|
Core()->seekPrev();
|
|
}
|
|
|
|
void DisassemblerGraphView::blockClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos)
|
|
{
|
|
RVA instr = getAddrForMouseEvent(block, &pos);
|
|
if(instr == RVA_INVALID)
|
|
{
|
|
return;
|
|
}
|
|
|
|
seek(instr, true);
|
|
|
|
if(event->button() == Qt::RightButton)
|
|
{
|
|
mMenu->setOffset(instr);
|
|
mMenu->exec(event->globalPos());
|
|
}
|
|
}
|
|
|
|
void DisassemblerGraphView::blockDoubleClicked(GraphView::GraphBlock &block, QMouseEvent *event, QPoint pos)
|
|
{
|
|
Q_UNUSED(event);
|
|
RVA instr = getAddrForMouseEvent(block, &pos);
|
|
if(instr == RVA_INVALID)
|
|
{
|
|
return;
|
|
}
|
|
QList<XrefDescription> refs = Core()->getXRefs(instr, false, false);
|
|
if (refs.length()) {
|
|
sent_seek = false;
|
|
Core()->seek(refs.at(0).to);
|
|
}
|
|
if (refs.length() > 1) {
|
|
qWarning() << "Too many references here. Weird behaviour expected.";
|
|
}
|
|
}
|
|
|
|
void DisassemblerGraphView::blockHelpEvent(GraphView::GraphBlock &block, QHelpEvent *event, QPoint pos)
|
|
{
|
|
Instr *instr = getInstrForMouseEvent(block, &pos);
|
|
if(!instr || instr->fullText.lines.empty())
|
|
{
|
|
QToolTip::hideText();
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
QToolTip::showText(event->globalPos(), instr->fullText.ToQString());
|
|
}
|
|
|
|
bool DisassemblerGraphView::helpEvent(QHelpEvent *event)
|
|
{
|
|
if(!GraphView::helpEvent(event))
|
|
{
|
|
QToolTip::hideText();
|
|
event->ignore();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void DisassemblerGraphView::blockTransitionedTo(GraphView::GraphBlock *to)
|
|
{
|
|
if(transition_dont_seek)
|
|
{
|
|
transition_dont_seek = false;
|
|
return;
|
|
}
|
|
seek(to->entry);
|
|
}
|
|
|
|
void DisassemblerGraphView::on_actionExportGraph_triggered()
|
|
{
|
|
QString fileName = QFileDialog::getSaveFileName(this,
|
|
tr("Export Graph"), "", tr("Dot file (*.dot)"));
|
|
QFile file(fileName);
|
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)){
|
|
qWarning() << "Can't open file";
|
|
return;
|
|
}
|
|
QTextStream fileOut(&file);
|
|
fileOut << Core()->cmd("ag -");
|
|
}
|