1260 lines
42 KiB
C++
1260 lines
42 KiB
C++
#include "DisassemblerGraphView.h"
|
|
#include <vector>
|
|
#include <QPainter>
|
|
#include <QScrollBar>
|
|
#include <QClipboard>
|
|
#include <QApplication>
|
|
#include <QMimeData>
|
|
|
|
DisassemblerGraphView::DisassemblerGraphView(QWidget* parent)
|
|
: QAbstractScrollArea(parent),
|
|
mFontMetrics(nullptr)
|
|
{
|
|
this->status = "Loading...";
|
|
|
|
//Create analysis and start it in another thread
|
|
//Dummy
|
|
|
|
//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->blocks.clear();
|
|
//this->show_il = false;
|
|
//this->simulation = None;
|
|
|
|
//Create timer to automatically refresh view when it needs to be updated
|
|
this->updateTimer = new QTimer();
|
|
this->updateTimer->setInterval(100);
|
|
this->updateTimer->setSingleShot(false);
|
|
connect(this->updateTimer, SIGNAL(timeout()), this, SLOT(updateTimerEvent()));
|
|
this->updateTimer->start();
|
|
|
|
this->initFont();
|
|
|
|
//Initialize scroll bars
|
|
this->width = 0;
|
|
this->height = 0;
|
|
this->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
|
this->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
|
this->horizontalScrollBar()->setSingleStep(this->charWidth);
|
|
this->verticalScrollBar()->setSingleStep(this->charHeight);
|
|
QSize areaSize = this->viewport()->size();
|
|
this->adjustSize(areaSize.width(), areaSize.height());
|
|
|
|
//Setup context menu
|
|
setupContextMenu();
|
|
|
|
//Connect to bridge
|
|
connect(Bridge::getBridge(), SIGNAL(loadGraph(BridgeCFGraphList*)), this, SLOT(loadGraphSlot(BridgeCFGraphList*)));
|
|
connect(Bridge::getBridge(), SIGNAL(graphAt(duint)), this, SLOT(graphAtSlot(duint)));
|
|
connect(Bridge::getBridge(), SIGNAL(updateGraph()), this, SLOT(updateGraphSlot()));
|
|
}
|
|
|
|
void DisassemblerGraphView::initFont()
|
|
{
|
|
setFont(QFont("Courier New", 8));
|
|
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<duint, duint> DisassemblerGraphView::get_selection_range()
|
|
{
|
|
return std::make_tuple<duint, duint>(get_cursor_pos(), get_cursor_pos());
|
|
}
|
|
|
|
void DisassemblerGraphView::set_selection_range(std::tuple<duint, duint> range)
|
|
{
|
|
this->set_cursor_pos(std::get<0>(range));
|
|
}
|
|
|
|
void DisassemblerGraphView::copy_address()
|
|
{
|
|
QClipboard* clipboard = QApplication::clipboard();
|
|
clipboard->clear();
|
|
QMimeData mime;
|
|
mime.setText(QString().sprintf("0x%p", this->get_cursor_pos()));
|
|
clipboard->setMimeData(&mime);
|
|
}
|
|
|
|
void DisassemblerGraphView::paintEvent(QPaintEvent* event)
|
|
{
|
|
Q_UNUSED(event);
|
|
QPainter p(this->viewport());
|
|
p.setFont(this->font());
|
|
|
|
int xofs = this->horizontalScrollBar()->value();
|
|
int yofs = this->verticalScrollBar()->value();
|
|
|
|
if(!this->ready)
|
|
{
|
|
//Analysis for the current function is not yet complete, paint loading screen
|
|
QLinearGradient gradient = QLinearGradient(QPointF(0, 0),
|
|
QPointF(this->viewport()->size().height(), this->viewport()->size().height()));
|
|
gradient.setColorAt(0, QColor(232, 232, 232));
|
|
gradient.setColorAt(1, QColor(192, 192, 192));
|
|
p.setPen(QColor(0, 0, 0, 0));
|
|
p.setBrush(QBrush(gradient));
|
|
p.drawRect(0, 0, this->viewport()->size().width(), this->viewport()->size().height());
|
|
|
|
QString text = this->function ? this->status : "No function selected";
|
|
p.setPen(Qt::black);
|
|
p.drawText((this->viewport()->size().width() / 2) - ((text.length() * this->charWidth) / 2),
|
|
(this->viewport()->size().height() / 2) + this->charOffset + this->baseline - (this->charHeight / 2),
|
|
text);
|
|
return;
|
|
}
|
|
|
|
//Render background
|
|
QLinearGradient gradient = QLinearGradient(QPointF(-xofs, -yofs), QPointF(this->renderWidth - xofs, this->renderHeight - yofs));
|
|
gradient.setColorAt(0, QColor(232, 232, 232));
|
|
gradient.setColorAt(1, QColor(192, 192, 192));
|
|
p.setPen(QColor(0, 0, 0, 0));
|
|
p.setBrush(QBrush(gradient));
|
|
p.drawRect(0, 0, this->viewport()->size().width(), this->viewport()->size().height());
|
|
|
|
p.translate(this->renderXOfs - xofs, this->renderYOfs - yofs);
|
|
|
|
//Render each node
|
|
for(auto & blockIt : this->blocks)
|
|
{
|
|
DisassemblerBlock & block = blockIt.second;
|
|
|
|
QRect blockRect = QRect(block.x + this->charWidth + 4, block.y + this->charWidth + 4,
|
|
block.width - (4 + 2 * this->charWidth), block.height - (4 + 2 * this->charWidth));
|
|
|
|
//Render shadow
|
|
p.setPen(QColor(0, 0, 0, 0));
|
|
p.setBrush(QColor(0, 0, 0, 128));
|
|
p.drawRect(blockRect);
|
|
|
|
//Render node background
|
|
p.setPen(Qt::black);
|
|
p.setBrush(QBrush(ConfigColor("DisassemblyBackgroundColor")));
|
|
p.drawRect(blockRect);
|
|
|
|
//Print current instruction background
|
|
if(this->cur_instr != 0)
|
|
{
|
|
int y = block.y + (2 * this->charWidth) + (int(block.block.header_text.lines.size()) * this->charHeight);
|
|
for(Instr & instr : block.block.instrs)
|
|
{
|
|
if(instr.addr == this->cur_instr)
|
|
{
|
|
p.setPen(QColor(0, 0, 0, 0));
|
|
p.setBrush(ConfigColor("DisassemblySelectionColor"));
|
|
p.drawRect(block.x + this->charWidth + 3, y, block.width - (10 + 2 * this->charWidth),
|
|
int(instr.text.lines.size()) * this->charHeight);
|
|
}
|
|
y += int(instr.text.lines.size()) * this->charHeight;
|
|
}
|
|
}
|
|
|
|
//Render highlighted tokens
|
|
//TODO
|
|
/*if(this->highlight_token)
|
|
{
|
|
int x = block.x + (2 * this->charWidth);
|
|
int y = block.y + (2 * this->charWidth);
|
|
for(auto & line : block.block.header_text.tokens)
|
|
{
|
|
for(Token & token : line)
|
|
{
|
|
if(this->highlight_token->equalsToken(token))
|
|
{
|
|
p.setPen(QColor(0, 0, 0, 0));
|
|
p.setBrush(QColor(192, 0, 0, 64));
|
|
p.drawRect(x + token.start * this->charWidth, y,
|
|
token.length * this->charWidth, this->charHeight);
|
|
}
|
|
}
|
|
y += this->charHeight;
|
|
}
|
|
for(Instr & instr : block.block.instrs)
|
|
{
|
|
for(auto & line : instr.text.tokens)
|
|
{
|
|
for(Token & token : line)
|
|
{
|
|
if(this->highlight_token->equalsToken(token))
|
|
{
|
|
p.setPen(QColor(0, 0, 0, 0));
|
|
p.setBrush(QColor(192, 0, 0, 64));
|
|
p.drawRect(x + token.start * this->charWidth, y,
|
|
token.length * this->charWidth, this->charHeight);
|
|
}
|
|
}
|
|
y += 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);
|
|
/*auto partx = x;
|
|
for(RichTextPainter::CustomRichText_t & part : line)
|
|
{
|
|
p.setPen(part.color);
|
|
p.drawText(partx, y + this->charOffset + this->baseline, part.text);
|
|
partx += part.text.length() * this->charWidth;
|
|
}*/
|
|
y += this->charHeight;
|
|
}
|
|
for(Instr & instr : block.block.instrs)
|
|
{
|
|
for(auto & line : instr.text.lines)
|
|
{
|
|
RichTextPainter::paintRichText(&p, x, y, block.width, this->charHeight, 0, line, mFontMetrics);
|
|
/*auto partx = x;
|
|
for(Line & part : line)
|
|
{
|
|
p.setPen(part.color);
|
|
p.drawText(partx, y + this->charOffset + this->baseline, part.text);
|
|
partx += part.text.length() * this->charWidth;
|
|
}*/
|
|
y += this->charHeight;
|
|
}
|
|
}
|
|
|
|
// Render edges
|
|
for(DisassemblerEdge & edge : block.edges)
|
|
{
|
|
p.setPen(edge.color);
|
|
p.setBrush(edge.color);
|
|
p.drawPolyline(edge.polyline);
|
|
p.drawConvexPolygon(edge.arrow);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool DisassemblerGraphView::isMouseEventInBlock(QMouseEvent* event)
|
|
{
|
|
//Convert coordinates to system used in blocks
|
|
int xofs = this->horizontalScrollBar()->value();
|
|
int yofs = this->verticalScrollBar()->value();
|
|
int x = event->x() + xofs - this->renderXOfs;
|
|
int y = event->y() + yofs - this->renderYOfs;
|
|
|
|
// Check each block for hits
|
|
for(auto & blockIt : this->blocks)
|
|
{
|
|
DisassemblerBlock & block = blockIt.second;
|
|
//Compute coordinate relative to text area in block
|
|
int blockx = x - (block.x + (2 * this->charWidth));
|
|
int blocky = y - (block.y + (2 * this->charWidth));
|
|
//Check to see if click is within bounds of block
|
|
if((blockx < 0) || (blockx > (block.width - 4 * this->charWidth)))
|
|
continue;
|
|
if((blocky < 0) || (blocky > (block.height - 4 * this->charWidth)))
|
|
continue;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
duint DisassemblerGraphView::getInstrForMouseEvent(QMouseEvent* event)
|
|
{
|
|
//Convert coordinates to system used in blocks
|
|
int xofs = this->horizontalScrollBar()->value();
|
|
int yofs = this->verticalScrollBar()->value();
|
|
int x = event->x() + xofs - this->renderXOfs;
|
|
int y = event->y() + yofs - this->renderYOfs;
|
|
|
|
//Check each block for hits
|
|
for(auto & blockIt : this->blocks)
|
|
{
|
|
DisassemblerBlock & block = blockIt.second;
|
|
//Compute coordinate relative to text area in block
|
|
int blockx = x - (block.x + (2 * this->charWidth));
|
|
int blocky = y - (block.y + (2 * this->charWidth));
|
|
//Check to see if click is within bounds of block
|
|
if((blockx < 0) || (blockx > (block.width - 4 * this->charWidth)))
|
|
continue;
|
|
if((blocky < 0) || (blocky > (block.height - 4 * this->charWidth)))
|
|
continue;
|
|
//Compute row within text
|
|
int row = int(blocky / this->charHeight);
|
|
//Determine instruction for this row
|
|
int cur_row = int(block.block.header_text.lines.size());
|
|
if(row < cur_row)
|
|
return block.block.entry;
|
|
for(Instr & instr : block.block.instrs)
|
|
{
|
|
if(row < cur_row + int(instr.text.lines.size()))
|
|
return instr.addr;
|
|
cur_row += int(instr.text.lines.size());
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool DisassemblerGraphView::getTokenForMouseEvent(QMouseEvent* event, Token & tokenOut)
|
|
{
|
|
/* TODO
|
|
//Convert coordinates to system used in blocks
|
|
int xofs = this->horizontalScrollBar()->value();
|
|
int yofs = this->verticalScrollBar()->value();
|
|
int x = event->x() + xofs - this->renderXOfs;
|
|
int y = event->y() + yofs - this->renderYOfs;
|
|
|
|
//Check each block for hits
|
|
for(auto & blockIt : this->blocks)
|
|
{
|
|
DisassemblerBlock & block = blockIt.second;
|
|
//Compute coordinate relative to text area in block
|
|
int blockx = x - (block.x + (2 * this->charWidth));
|
|
int blocky = y - (block.y + (2 * this->charWidth));
|
|
//Check to see if click is within bounds of block
|
|
if((blockx < 0) || (blockx > (block.width - 4 * this->charWidth)))
|
|
continue;
|
|
if((blocky < 0) || (blocky > (block.height - 4 * this->charWidth)))
|
|
continue;
|
|
//Compute row and column within text
|
|
int col = int(blockx / this->charWidth);
|
|
int row = int(blocky / this->charHeight);
|
|
//Check tokens to see if one was clicked
|
|
int cur_row = 0;
|
|
for(auto & line : block.block.header_text.tokens)
|
|
{
|
|
if(cur_row == row)
|
|
{
|
|
for(Token & token : line)
|
|
{
|
|
if((col >= token.start) && (col < (token.start + token.length)))
|
|
{
|
|
//Clicked on a token
|
|
tokenOut = token;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
cur_row += 1;
|
|
}
|
|
for(Instr & instr : block.block.instrs)
|
|
{
|
|
for(auto & line : instr.text.tokens)
|
|
{
|
|
if(cur_row == row)
|
|
{
|
|
for(Token & token : line)
|
|
{
|
|
if((col >= token.start) && (col < (token.start + token.length)))
|
|
{
|
|
//Clicked on a token
|
|
tokenOut = token;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
cur_row += 1;
|
|
}
|
|
}
|
|
}*/
|
|
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(event->button() != Qt::LeftButton && event->button() != Qt::RightButton)
|
|
return;
|
|
|
|
if(!this->isMouseEventInBlock(event))
|
|
{
|
|
//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();
|
|
return;
|
|
}
|
|
|
|
//Check for click on a token and highlight it
|
|
Token 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;
|
|
else
|
|
this->cur_instr = 0;
|
|
|
|
this->viewport()->update();
|
|
|
|
if((instr != 0) && (event->button() == Qt::RightButton))
|
|
{
|
|
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();
|
|
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::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);
|
|
Token token;
|
|
if(this->getTokenForMouseEvent(event, token))
|
|
{
|
|
if(!this->analysis.functions.count(token.addr))
|
|
{
|
|
//Not a function or not analyzed, go to address in hex editor
|
|
//TODO: show in hex editor?
|
|
}
|
|
else
|
|
{
|
|
this->function = token.addr;
|
|
this->ready = false;
|
|
this->desired_pos = nullptr;
|
|
this->cur_instr = 0;
|
|
this->highlight_token = nullptr;
|
|
this->viewport()->update();
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
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;
|
|
for(duint edge : block.new_exits)
|
|
{
|
|
this->computeGraphLayout(this->blocks[edge]);
|
|
this->adjustGraphLayout(this->blocks[edge], col, 1);
|
|
col += this->blocks[edge].col_count;
|
|
if((this->blocks[edge].row_count + 1) > row_count)
|
|
row_count = this->blocks[edge].row_count + 1;
|
|
}
|
|
|
|
block.row = 0;
|
|
if(col >= 2)
|
|
{
|
|
//Place this node centered over the child nodes
|
|
block.col = (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_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)
|
|
{
|
|
while(int(edges[row][col].size()) <= index)
|
|
edges[row][col].push_back(false);
|
|
edges[row][col][index] = true;
|
|
}
|
|
|
|
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++) //TODO: use max_col+1 ?
|
|
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++) //TODO: use max_col+1 ?
|
|
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++) //TODO: use max_row+1 ?
|
|
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++) //TODO: use max_row+1 ?
|
|
this->markEdge(edges, row, col, i);
|
|
return i;
|
|
}
|
|
|
|
DisassemblerGraphView::DisassemblerEdge DisassemblerGraphView::routeEdge(EdgesVector & horiz_edges, EdgesVector & vert_edges, Matrix<bool> & 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)
|
|
{
|
|
int ofs = 0;
|
|
while(true)
|
|
{
|
|
col = start.col + 1 - ofs;
|
|
if(col >= 0)
|
|
{
|
|
bool valid = true;
|
|
for(int row = min_row; row < max_row + 1; row++)
|
|
{
|
|
if(!edge_valid[row][col])
|
|
{
|
|
valid = false;
|
|
break;
|
|
}
|
|
}
|
|
if(valid)
|
|
break;
|
|
}
|
|
|
|
col = start.col + 1 + ofs;
|
|
if(col < int(edge_valid[min_row].size()))
|
|
{
|
|
bool valid = true;
|
|
for(int row = min_row; row < max_row + 1; row++)
|
|
{
|
|
if(!edge_valid[row][col])
|
|
{
|
|
valid = false;
|
|
break;
|
|
}
|
|
}
|
|
if(valid)
|
|
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
|
|
int index = this->findVertEdgeIndex(vert_edges, col, min_row, max_row);
|
|
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<class T>
|
|
static void removeFromVec(std::vector<T> & vec, T elem)
|
|
{
|
|
vec.erase(std::remove(vec.begin(), vec.end(), elem), vec.end());
|
|
}
|
|
|
|
template<class T>
|
|
static void initVec(std::vector<T> & vec, size_t size, T value)
|
|
{
|
|
vec.resize(size);
|
|
for(size_t i = 0; i < size; i++)
|
|
vec[i] = value;
|
|
}
|
|
|
|
void DisassemblerGraphView::renderFunction(Function & func)
|
|
{
|
|
puts("Starting renderFunction");
|
|
|
|
//Create render nodes
|
|
this->blocks.clear();
|
|
for(Block & block : func.blocks)
|
|
{
|
|
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
|
|
//auto block = func.blocks[func.entry];
|
|
std::unordered_set<duint> visited;
|
|
visited.insert(func.entry);
|
|
std::queue<duint> queue;
|
|
queue.push(this->blocks[func.entry].block.entry);
|
|
auto changed = true;
|
|
|
|
int best_edges;
|
|
duint best_parent;
|
|
while(changed)
|
|
{
|
|
changed = false;
|
|
|
|
//First pick nodes that have single entry points
|
|
while(!queue.empty())
|
|
{
|
|
DisassemblerBlock & block = this->blocks[queue.front()];
|
|
queue.pop();
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
//No more nodes satisfy constraints, pick a node to continue constructing the graph
|
|
duint best = 0;
|
|
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);
|
|
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]);
|
|
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<bool> 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(auto & blockIt : this->blocks)
|
|
{
|
|
DisassemblerBlock & block = blockIt.second;
|
|
DisassemblerBlock & start = block;
|
|
for(duint edge : block.block.exits)
|
|
{
|
|
DisassemblerBlock & end = this->blocks[edge];
|
|
QColor color(Qt::black);
|
|
if(edge == block.block.true_path)
|
|
color = QColor(0, 144, 0);
|
|
else if(edge == block.block.false_path)
|
|
color = QColor(144, 0, 0);
|
|
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<int> 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<int> 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<int> 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();
|
|
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()
|
|
{
|
|
auto status = this->analysis.status;
|
|
if(status != this->status)
|
|
{
|
|
this->status = status;
|
|
this->viewport()->update();
|
|
}
|
|
|
|
if(this->function == 0)
|
|
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()
|
|
{
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
//Check to see if address is within current function
|
|
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((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::loadGraphSlot(BridgeCFGraphList* graphList)
|
|
{
|
|
BridgeCFGraph graph(graphList);
|
|
Analysis anal;
|
|
QBeaEngine disasm(int(ConfigUint("Disassembler", "MaxModuleSize")));
|
|
anal.update_id = this->update_id + 1;
|
|
anal.entry = graph.entryPoint;
|
|
anal.ready = true;
|
|
{
|
|
Function func;
|
|
func.entry = graph.entryPoint;
|
|
func.ready = true;
|
|
func.update_id = anal.update_id;
|
|
{
|
|
for(const auto & nodeIt : graph.nodes)
|
|
{
|
|
const BridgeCFNode & node = nodeIt.second;
|
|
Block block;
|
|
block.entry = node.start;
|
|
block.exits = node.exits;
|
|
block.false_path = node.brfalse;
|
|
block.true_path = node.brtrue;
|
|
block.header_text = Text(ToPtrString(block.entry), Qt::red);
|
|
{
|
|
Instr instr;
|
|
unsigned char data[MAX_DISASM_BUFFER];
|
|
for(size_t i = 0; i < node.data.size();)
|
|
{
|
|
data[0] = 0xFF;
|
|
memcpy(data, node.data.data() + i, qMin(sizeof(data), node.data.size() - i));
|
|
auto addr = node.start + i;
|
|
Instruction_t instrTok = disasm.DisassembleAt((byte_t*)data, sizeof(data), 0, addr);
|
|
RichTextPainter::List richText;
|
|
CapstoneTokenizer::TokenToRichText(instrTok.tokens, richText, 0);
|
|
auto size = instrTok.length;
|
|
instr.addr = addr;
|
|
instr.opcode.resize(size);
|
|
for(int j = 0; j < size; j++)
|
|
instr.opcode[j] = data[j];
|
|
instr.text = Text(richText);
|
|
block.instrs.push_back(instr);
|
|
i += size;
|
|
}
|
|
}
|
|
func.blocks.push_back(block);
|
|
}
|
|
}
|
|
anal.functions.insert({func.entry, func});
|
|
}
|
|
this->analysis = anal;
|
|
this->function = this->analysis.entry;
|
|
Bridge::getBridge()->setResult();
|
|
}
|
|
|
|
void DisassemblerGraphView::graphAtSlot(duint addr)
|
|
{
|
|
this->cur_instr = addr;
|
|
//this->navigate(addr);
|
|
}
|
|
|
|
void DisassemblerGraphView::updateGraphSlot()
|
|
{
|
|
this->viewport()->update();
|
|
}
|
|
|
|
void DisassemblerGraphView::setupContextMenu()
|
|
{
|
|
mMenuBuilder = new MenuBuilder(this, [](QMenu*)
|
|
{
|
|
return DbgIsDebugging();
|
|
});
|
|
|
|
mMenuBuilder->addAction(makeAction(DIcon(QString("processor%1.png").arg(ArchValue("32", "64"))), tr("Follow in &Disassembler"), SLOT(followDisassemblerSlot())), [this](QMenu*)
|
|
{
|
|
return this->cur_instr != 0;
|
|
});
|
|
}
|
|
|
|
void DisassemblerGraphView::followDisassemblerSlot()
|
|
{
|
|
DbgCmdExec(QString("disasm %1").arg(ToPtrString(this->cur_instr)).toUtf8().constData());
|
|
emit showCpu();
|
|
}
|