2017-10-12 19:55:15 +00:00
|
|
|
#include "DisassemblyWidget.h"
|
2017-10-11 21:07:32 +00:00
|
|
|
#include "menus/DisassemblyContextMenu.h"
|
2018-10-17 07:55:53 +00:00
|
|
|
#include "common/Configuration.h"
|
|
|
|
#include "common/Helpers.h"
|
|
|
|
#include "common/TempConfig.h"
|
2019-07-12 08:57:07 +00:00
|
|
|
#include "common/SelectionHighlight.h"
|
2021-02-15 06:46:57 +00:00
|
|
|
#include "common/BinaryTrees.h"
|
2019-06-18 17:57:07 +00:00
|
|
|
#include "core/MainWindow.h"
|
2017-11-02 06:48:32 +00:00
|
|
|
|
2019-06-29 22:26:48 +00:00
|
|
|
#include <QApplication>
|
2017-10-16 19:00:47 +00:00
|
|
|
#include <QScrollBar>
|
2017-11-02 06:48:32 +00:00
|
|
|
#include <QJsonArray>
|
|
|
|
#include <QJsonObject>
|
|
|
|
#include <QVBoxLayout>
|
2017-11-03 11:31:20 +00:00
|
|
|
#include <QRegularExpression>
|
2020-12-05 07:26:26 +00:00
|
|
|
#include <QToolTip>
|
2017-12-13 14:30:00 +00:00
|
|
|
#include <QTextBlockUserData>
|
2019-06-26 07:12:39 +00:00
|
|
|
#include <QPainter>
|
2020-06-04 03:51:03 +00:00
|
|
|
#include <QPainterPath>
|
2019-06-26 07:12:39 +00:00
|
|
|
#include <QSplitter>
|
2017-12-13 14:30:00 +00:00
|
|
|
|
2021-02-15 06:46:57 +00:00
|
|
|
#include <algorithm>
|
|
|
|
#include <cmath>
|
|
|
|
|
|
|
|
class DisassemblyTextBlockUserData: public QTextBlockUserData
|
2017-12-13 14:30:00 +00:00
|
|
|
{
|
|
|
|
public:
|
|
|
|
DisassemblyLine line;
|
|
|
|
|
2021-01-24 14:50:13 +00:00
|
|
|
explicit DisassemblyTextBlockUserData(const DisassemblyLine &line) { this->line = line; }
|
2017-12-13 14:30:00 +00:00
|
|
|
};
|
2017-11-02 06:48:32 +00:00
|
|
|
|
2017-12-16 13:01:58 +00:00
|
|
|
static DisassemblyTextBlockUserData *getUserData(const QTextBlock &block)
|
|
|
|
{
|
|
|
|
QTextBlockUserData *userData = block.userData();
|
2018-03-21 20:32:32 +00:00
|
|
|
if (!userData) {
|
2017-12-16 13:01:58 +00:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return static_cast<DisassemblyTextBlockUserData *>(userData);
|
|
|
|
}
|
|
|
|
|
2020-05-22 11:49:34 +00:00
|
|
|
DisassemblyWidget::DisassemblyWidget(MainWindow *main)
|
2021-01-24 14:50:13 +00:00
|
|
|
: MemoryDockWidget(MemoryWidgetType::Disassembly, main),
|
|
|
|
mCtxMenu(new DisassemblyContextMenu(this, main)),
|
|
|
|
mDisasScrollArea(new DisassemblyScrollArea(this)),
|
|
|
|
mDisasTextEdit(new DisassemblyTextEdit(this))
|
|
|
|
{
|
|
|
|
setObjectName(main ? main->getUniqueObjectName(getWidgetType()) : getWidgetType());
|
2020-08-29 05:15:47 +00:00
|
|
|
updateWindowTitle();
|
2019-03-18 06:44:14 +00:00
|
|
|
|
2017-11-02 06:48:32 +00:00
|
|
|
topOffset = bottomOffset = RVA_INVALID;
|
2017-12-16 13:01:58 +00:00
|
|
|
cursorLineOffset = 0;
|
2019-04-22 08:48:06 +00:00
|
|
|
cursorCharOffset = 0;
|
2017-12-16 13:01:58 +00:00
|
|
|
seekFromCursor = false;
|
2017-11-02 06:48:32 +00:00
|
|
|
|
2019-06-26 07:12:39 +00:00
|
|
|
// Instantiate the window layout
|
|
|
|
auto *splitter = new QSplitter;
|
|
|
|
|
|
|
|
// Setup the left frame that contains breakpoints and jumps
|
|
|
|
leftPanel = new DisassemblyLeftPanel(this);
|
|
|
|
splitter->addWidget(leftPanel);
|
|
|
|
|
|
|
|
// Setup the disassembly content
|
|
|
|
auto *layout = new QHBoxLayout;
|
2017-11-02 06:48:32 +00:00
|
|
|
layout->addWidget(mDisasTextEdit);
|
|
|
|
layout->setMargin(0);
|
|
|
|
mDisasScrollArea->viewport()->setLayout(layout);
|
2019-06-26 07:12:39 +00:00
|
|
|
splitter->addWidget(mDisasScrollArea);
|
2019-06-29 22:26:48 +00:00
|
|
|
mDisasScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
|
|
|
|
// Use stylesheet instead of QWidget::setFrameShape(QFrame::NoShape) to avoid
|
|
|
|
// issues with dark and light interface themes
|
2019-07-18 06:05:29 +00:00
|
|
|
mDisasScrollArea->setStyleSheet("QAbstractScrollArea { border: 0px transparent black; }");
|
|
|
|
mDisasTextEdit->setStyleSheet("QPlainTextEdit { border: 0px transparent black; }");
|
2019-06-29 22:26:48 +00:00
|
|
|
mDisasTextEdit->setFocusProxy(this);
|
|
|
|
mDisasTextEdit->setFocusPolicy(Qt::ClickFocus);
|
|
|
|
mDisasScrollArea->setFocusProxy(this);
|
|
|
|
mDisasScrollArea->setFocusPolicy(Qt::ClickFocus);
|
|
|
|
|
|
|
|
setFocusPolicy(Qt::ClickFocus);
|
2019-07-14 00:52:43 +00:00
|
|
|
|
2019-06-29 22:26:48 +00:00
|
|
|
// Behave like all widgets: highlight on focus and hover
|
2021-01-24 14:50:13 +00:00
|
|
|
connect(qApp, &QApplication::focusChanged, this, [this](QWidget *, QWidget *now) {
|
|
|
|
QColor borderColor = this == now ? palette().color(QPalette::Highlight)
|
|
|
|
: palette().color(QPalette::WindowText).darker();
|
2019-06-29 22:26:48 +00:00
|
|
|
widget()->setStyleSheet(QString("QSplitter { border: %1px solid %2 } \n"
|
|
|
|
"QSplitter:hover { border: %1px solid %3 } \n")
|
2021-01-24 14:50:13 +00:00
|
|
|
.arg(devicePixelRatio())
|
|
|
|
.arg(borderColor.name())
|
|
|
|
.arg(palette().color(QPalette::Highlight).name()));
|
2019-06-29 22:26:48 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
splitter->setFrameShape(QFrame::Box);
|
2019-06-26 07:12:39 +00:00
|
|
|
// Set current widget to the splitted layout we just created
|
|
|
|
setWidget(splitter);
|
2017-11-02 06:48:32 +00:00
|
|
|
|
2019-06-26 07:12:39 +00:00
|
|
|
// Resize properly
|
|
|
|
QList<int> sizes;
|
|
|
|
sizes.append(3);
|
2019-06-29 22:26:48 +00:00
|
|
|
sizes.append(1);
|
2019-06-26 07:12:39 +00:00
|
|
|
splitter->setSizes(sizes);
|
2017-11-02 06:48:32 +00:00
|
|
|
|
2017-10-12 19:55:15 +00:00
|
|
|
setAllowedAreas(Qt::AllDockWidgetAreas);
|
2017-11-30 14:00:22 +00:00
|
|
|
|
|
|
|
setupFonts();
|
|
|
|
setupColors();
|
|
|
|
|
2021-01-24 14:50:13 +00:00
|
|
|
disasmRefresh = createReplacingRefreshDeferrer<RVA>(
|
|
|
|
false, [this](const RVA *offset) { refreshDisasm(offset ? *offset : RVA_INVALID); });
|
2019-01-12 19:25:43 +00:00
|
|
|
|
2017-11-30 14:00:22 +00:00
|
|
|
maxLines = 0;
|
|
|
|
updateMaxLines();
|
2017-10-11 21:07:32 +00:00
|
|
|
|
2017-11-02 06:48:32 +00:00
|
|
|
mDisasTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
2017-10-14 09:35:49 +00:00
|
|
|
mDisasTextEdit->setFont(Config()->getFont());
|
2017-10-16 19:00:47 +00:00
|
|
|
mDisasTextEdit->setReadOnly(true);
|
2017-11-02 06:48:32 +00:00
|
|
|
mDisasTextEdit->setLineWrapMode(QPlainTextEdit::WidgetWidth);
|
|
|
|
// wrapping breaks readCurrentDisassemblyOffset() at the moment :-(
|
|
|
|
mDisasTextEdit->setWordWrapMode(QTextOption::NoWrap);
|
2017-10-11 21:07:32 +00:00
|
|
|
|
|
|
|
// Increase asm text edit margin
|
|
|
|
QTextDocument *asm_docu = mDisasTextEdit->document();
|
|
|
|
asm_docu->setDocumentMargin(10);
|
|
|
|
|
|
|
|
// Event filter to intercept double clicks in the textbox
|
2020-12-05 07:26:26 +00:00
|
|
|
// and showing tooltips when hovering above those offsets
|
2017-10-11 21:07:32 +00:00
|
|
|
mDisasTextEdit->viewport()->installEventFilter(this);
|
|
|
|
|
|
|
|
// Set Disas context menu
|
|
|
|
mDisasTextEdit->setContextMenuPolicy(Qt::CustomContextMenu);
|
2021-01-24 14:50:13 +00:00
|
|
|
connect(mDisasTextEdit, &QWidget::customContextMenuRequested, this,
|
|
|
|
&DisassemblyWidget::showDisasContextMenu);
|
2017-11-04 11:46:29 +00:00
|
|
|
|
2021-01-24 14:50:13 +00:00
|
|
|
connect(mDisasScrollArea, &DisassemblyScrollArea::scrollLines, this,
|
|
|
|
&DisassemblyWidget::scrollInstructions);
|
|
|
|
connect(mDisasScrollArea, &DisassemblyScrollArea::disassemblyResized, this,
|
|
|
|
&DisassemblyWidget::updateMaxLines);
|
2017-11-02 06:48:32 +00:00
|
|
|
|
|
|
|
connectCursorPositionChanged(false);
|
2021-01-24 14:50:13 +00:00
|
|
|
connect(mDisasTextEdit->verticalScrollBar(), &QScrollBar::valueChanged, this, [=](int value) {
|
2018-03-21 20:32:32 +00:00
|
|
|
if (value != 0) {
|
2017-11-02 06:48:32 +00:00
|
|
|
mDisasTextEdit->verticalScrollBar()->setValue(0);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2021-01-24 14:50:13 +00:00
|
|
|
connect(Core(), &CutterCore::commentsChanged, this, [this]() { refreshDisasm(); });
|
2017-12-03 20:23:02 +00:00
|
|
|
connect(Core(), SIGNAL(flagsChanged()), this, SLOT(refreshDisasm()));
|
2017-12-11 13:07:12 +00:00
|
|
|
connect(Core(), SIGNAL(functionsChanged()), this, SLOT(refreshDisasm()));
|
2021-01-24 14:50:13 +00:00
|
|
|
connect(Core(), &CutterCore::functionRenamed, this, [this]() { refreshDisasm(); });
|
2017-11-30 21:30:51 +00:00
|
|
|
connect(Core(), SIGNAL(varsChanged()), this, SLOT(refreshDisasm()));
|
2017-11-07 08:16:49 +00:00
|
|
|
connect(Core(), SIGNAL(asmOptionsChanged()), this, SLOT(refreshDisasm()));
|
2020-08-02 09:51:56 +00:00
|
|
|
connect(Core(), &CutterCore::instructionChanged, this, &DisassemblyWidget::refreshIfInRange);
|
|
|
|
connect(Core(), &CutterCore::breakpointsChanged, this, &DisassemblyWidget::refreshIfInRange);
|
2018-07-01 21:29:38 +00:00
|
|
|
connect(Core(), SIGNAL(refreshCodeViews()), this, SLOT(refreshDisasm()));
|
2017-11-04 11:46:29 +00:00
|
|
|
|
2020-08-03 09:13:39 +00:00
|
|
|
connect(Config(), &Configuration::fontsUpdated, this, &DisassemblyWidget::fontsUpdatedSlot);
|
|
|
|
connect(Config(), &Configuration::colorsUpdated, this, &DisassemblyWidget::colorsUpdatedSlot);
|
2017-11-04 11:46:29 +00:00
|
|
|
|
2021-01-24 14:50:13 +00:00
|
|
|
connect(Core(), &CutterCore::refreshAll, this,
|
|
|
|
[this]() { refreshDisasm(seekable->getOffset()); });
|
2019-05-01 16:15:33 +00:00
|
|
|
refreshDisasm(seekable->getOffset());
|
2017-11-28 15:42:40 +00:00
|
|
|
|
2020-08-03 09:13:39 +00:00
|
|
|
connect(mCtxMenu, &DisassemblyContextMenu::copy, mDisasTextEdit, &QPlainTextEdit::copy);
|
2017-12-02 15:43:21 +00:00
|
|
|
|
2018-05-25 14:30:59 +00:00
|
|
|
mCtxMenu->addSeparator();
|
2019-10-06 17:35:44 +00:00
|
|
|
mCtxMenu->addAction(&syncAction);
|
2021-01-24 14:50:13 +00:00
|
|
|
connect(seekable, &CutterSeekable::seekableSeekChanged, this,
|
|
|
|
&DisassemblyWidget::on_seekChanged);
|
2017-12-16 13:01:58 +00:00
|
|
|
|
2019-05-01 16:15:33 +00:00
|
|
|
addActions(mCtxMenu->actions());
|
|
|
|
|
2021-01-24 14:50:13 +00:00
|
|
|
#define ADD_ACTION(ksq, ctx, slot) \
|
|
|
|
{ \
|
|
|
|
QAction *a = new QAction(this); \
|
|
|
|
a->setShortcut(ksq); \
|
|
|
|
a->setShortcutContext(ctx); \
|
|
|
|
addAction(a); \
|
|
|
|
connect(a, &QAction::triggered, this, (slot)); \
|
|
|
|
}
|
2019-05-01 16:15:33 +00:00
|
|
|
|
|
|
|
// Space to switch to graph
|
2021-01-24 14:50:13 +00:00
|
|
|
ADD_ACTION(Qt::Key_Space, Qt::WidgetWithChildrenShortcut,
|
|
|
|
[this] { mainWindow->showMemoryWidget(MemoryWidgetType::Graph); })
|
2019-05-01 16:15:33 +00:00
|
|
|
|
|
|
|
ADD_ACTION(Qt::Key_Escape, Qt::WidgetWithChildrenShortcut, &DisassemblyWidget::seekPrev)
|
|
|
|
|
2021-01-24 14:50:13 +00:00
|
|
|
ADD_ACTION(Qt::Key_J, Qt::WidgetWithChildrenShortcut,
|
|
|
|
[this]() { moveCursorRelative(false, false); })
|
|
|
|
ADD_ACTION(QKeySequence::MoveToNextLine, Qt::WidgetWithChildrenShortcut,
|
|
|
|
[this]() { moveCursorRelative(false, false); })
|
|
|
|
ADD_ACTION(Qt::Key_K, Qt::WidgetWithChildrenShortcut,
|
|
|
|
[this]() { moveCursorRelative(true, false); })
|
|
|
|
ADD_ACTION(QKeySequence::MoveToPreviousLine, Qt::WidgetWithChildrenShortcut,
|
|
|
|
[this]() { moveCursorRelative(true, false); })
|
|
|
|
ADD_ACTION(QKeySequence::MoveToNextPage, Qt::WidgetWithChildrenShortcut,
|
|
|
|
[this]() { moveCursorRelative(false, true); })
|
|
|
|
ADD_ACTION(QKeySequence::MoveToPreviousPage, Qt::WidgetWithChildrenShortcut,
|
|
|
|
[this]() { moveCursorRelative(true, true); })
|
2019-05-01 16:15:33 +00:00
|
|
|
#undef ADD_ACTION
|
2017-10-11 21:07:32 +00:00
|
|
|
}
|
|
|
|
|
2019-05-01 16:15:33 +00:00
|
|
|
void DisassemblyWidget::setPreviewMode(bool previewMode)
|
|
|
|
{
|
2021-01-24 14:50:13 +00:00
|
|
|
mDisasTextEdit->setContextMenuPolicy(previewMode ? Qt::NoContextMenu : Qt::CustomContextMenu);
|
2019-05-01 16:15:33 +00:00
|
|
|
mCtxMenu->setEnabled(!previewMode);
|
|
|
|
for (auto action : mCtxMenu->actions()) {
|
|
|
|
action->setEnabled(!previewMode);
|
|
|
|
}
|
|
|
|
for (auto action : actions()) {
|
2021-01-24 14:50:13 +00:00
|
|
|
if (action->shortcut() == Qt::Key_Space || action->shortcut() == Qt::Key_Escape) {
|
2019-05-01 16:15:33 +00:00
|
|
|
action->setEnabled(!previewMode);
|
|
|
|
}
|
|
|
|
}
|
2019-06-18 13:02:41 +00:00
|
|
|
if (previewMode) {
|
|
|
|
seekable->setSynchronization(false);
|
2019-05-01 16:15:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
QWidget *DisassemblyWidget::getTextWidget()
|
2017-10-22 10:21:44 +00:00
|
|
|
{
|
|
|
|
return mDisasTextEdit;
|
|
|
|
}
|
|
|
|
|
2019-06-18 13:02:41 +00:00
|
|
|
QString DisassemblyWidget::getWidgetType()
|
|
|
|
{
|
|
|
|
return "Disassembly";
|
|
|
|
}
|
|
|
|
|
2019-06-26 07:12:39 +00:00
|
|
|
QFontMetrics DisassemblyWidget::getFontMetrics()
|
|
|
|
{
|
|
|
|
return mDisasTextEdit->fontMetrics();
|
|
|
|
}
|
|
|
|
|
|
|
|
QList<DisassemblyLine> DisassemblyWidget::getLines()
|
|
|
|
{
|
|
|
|
return lines;
|
|
|
|
}
|
|
|
|
|
2020-08-02 09:51:56 +00:00
|
|
|
void DisassemblyWidget::refreshIfInRange(RVA offset)
|
|
|
|
{
|
|
|
|
if (offset >= topOffset && offset <= bottomOffset) {
|
|
|
|
refreshDisasm();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-02 06:48:32 +00:00
|
|
|
void DisassemblyWidget::refreshDisasm(RVA offset)
|
2017-10-22 13:55:42 +00:00
|
|
|
{
|
2021-01-24 14:50:13 +00:00
|
|
|
if (!disasmRefresh->attemptRefresh(offset == RVA_INVALID ? nullptr : new RVA(offset))) {
|
2019-01-12 18:01:43 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
if (offset != RVA_INVALID) {
|
2017-11-02 06:48:32 +00:00
|
|
|
topOffset = offset;
|
|
|
|
}
|
2017-10-11 21:07:32 +00:00
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
if (topOffset == RVA_INVALID) {
|
2017-12-04 13:11:05 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
if (maxLines <= 0) {
|
2017-11-18 12:56:48 +00:00
|
|
|
connectCursorPositionChanged(true);
|
2017-11-02 06:48:32 +00:00
|
|
|
mDisasTextEdit->clear();
|
2017-11-18 12:56:48 +00:00
|
|
|
connectCursorPositionChanged(false);
|
2017-11-02 06:48:32 +00:00
|
|
|
return;
|
|
|
|
}
|
2017-10-11 21:07:32 +00:00
|
|
|
|
2018-08-12 16:20:16 +00:00
|
|
|
breakpoints = Core()->getBreakpointsAddresses();
|
2017-11-02 06:48:32 +00:00
|
|
|
int horizontalScrollValue = mDisasTextEdit->horizontalScrollBar()->value();
|
|
|
|
mDisasTextEdit->setLockScroll(true); // avoid flicker
|
2018-03-21 20:32:32 +00:00
|
|
|
|
2019-06-26 07:12:39 +00:00
|
|
|
// Retrieve disassembly lines
|
2017-12-13 14:30:00 +00:00
|
|
|
{
|
|
|
|
TempConfig tempConfig;
|
2021-01-24 14:50:13 +00:00
|
|
|
tempConfig.set("scr.color", COLOR_MODE_16M).set("asm.lines", false);
|
2019-06-26 07:12:39 +00:00
|
|
|
lines = Core()->disassembleLines(topOffset, maxLines);
|
2017-12-13 14:30:00 +00:00
|
|
|
}
|
2017-10-11 21:07:32 +00:00
|
|
|
|
2017-11-02 06:48:32 +00:00
|
|
|
connectCursorPositionChanged(true);
|
2017-10-22 13:55:42 +00:00
|
|
|
|
2017-12-13 14:30:00 +00:00
|
|
|
mDisasTextEdit->document()->clear();
|
|
|
|
QTextCursor cursor(mDisasTextEdit->document());
|
2018-08-12 16:20:16 +00:00
|
|
|
QTextBlockFormat regular = cursor.blockFormat();
|
2019-06-26 07:12:39 +00:00
|
|
|
for (const DisassemblyLine &line : lines) {
|
2018-04-15 17:06:05 +00:00
|
|
|
if (line.offset < topOffset) { // overflow
|
|
|
|
break;
|
|
|
|
}
|
2017-12-13 14:30:00 +00:00
|
|
|
cursor.insertHtml(line.text);
|
2018-09-30 20:00:53 +00:00
|
|
|
if (Core()->isBreakpoint(breakpoints, line.offset)) {
|
2018-08-12 16:20:16 +00:00
|
|
|
QTextBlockFormat f;
|
|
|
|
f.setBackground(ConfigColor("gui.breakpoint_background"));
|
|
|
|
cursor.setBlockFormat(f);
|
|
|
|
}
|
2017-12-13 14:30:00 +00:00
|
|
|
auto a = new DisassemblyTextBlockUserData(line);
|
|
|
|
cursor.block().setUserData(a);
|
|
|
|
cursor.insertBlock();
|
2018-08-12 16:20:16 +00:00
|
|
|
cursor.setBlockFormat(regular);
|
2017-12-13 14:30:00 +00:00
|
|
|
}
|
2017-10-11 21:07:32 +00:00
|
|
|
|
2019-06-26 07:12:39 +00:00
|
|
|
if (!lines.isEmpty()) {
|
|
|
|
bottomOffset = lines[qMin(lines.size(), maxLines) - 1].offset;
|
2018-04-15 17:06:05 +00:00
|
|
|
if (bottomOffset < topOffset) {
|
|
|
|
bottomOffset = RVA_MAX;
|
|
|
|
}
|
|
|
|
} else {
|
2017-11-02 06:48:32 +00:00
|
|
|
bottomOffset = topOffset;
|
|
|
|
}
|
2017-10-11 21:07:32 +00:00
|
|
|
|
2020-10-13 22:01:04 +00:00
|
|
|
connectCursorPositionChanged(false);
|
|
|
|
|
|
|
|
updateCursorPosition();
|
|
|
|
|
2017-11-20 10:04:03 +00:00
|
|
|
// remove additional lines
|
2018-04-15 17:06:05 +00:00
|
|
|
QTextCursor tc = mDisasTextEdit->textCursor();
|
|
|
|
tc.movePosition(QTextCursor::Start);
|
|
|
|
tc.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, maxLines - 1);
|
2017-11-20 10:04:03 +00:00
|
|
|
tc.movePosition(QTextCursor::EndOfLine);
|
|
|
|
tc.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
|
|
|
|
tc.removeSelectedText();
|
|
|
|
|
2017-11-02 06:48:32 +00:00
|
|
|
mDisasTextEdit->setLockScroll(false);
|
|
|
|
mDisasTextEdit->horizontalScrollBar()->setValue(horizontalScrollValue);
|
2019-06-26 07:12:39 +00:00
|
|
|
|
|
|
|
// Refresh the left panel (trigger paintEvent)
|
|
|
|
leftPanel->update();
|
2017-11-02 06:48:32 +00:00
|
|
|
}
|
2017-10-11 21:07:32 +00:00
|
|
|
|
2017-11-02 06:48:32 +00:00
|
|
|
void DisassemblyWidget::scrollInstructions(int count)
|
|
|
|
{
|
2018-03-21 20:32:32 +00:00
|
|
|
if (count == 0) {
|
2017-11-02 06:48:32 +00:00
|
|
|
return;
|
2017-10-22 13:55:42 +00:00
|
|
|
}
|
2017-10-11 21:07:32 +00:00
|
|
|
|
2017-11-02 06:48:32 +00:00
|
|
|
RVA offset;
|
2018-03-21 20:32:32 +00:00
|
|
|
if (count > 0) {
|
2017-11-02 06:48:32 +00:00
|
|
|
offset = Core()->nextOpAddr(topOffset, count);
|
2018-04-15 17:06:05 +00:00
|
|
|
if (offset < topOffset) {
|
|
|
|
offset = RVA_MAX;
|
|
|
|
}
|
2018-03-21 20:32:32 +00:00
|
|
|
} else {
|
2017-11-02 06:48:32 +00:00
|
|
|
offset = Core()->prevOpAddr(topOffset, -count);
|
2018-04-15 17:06:05 +00:00
|
|
|
if (offset > topOffset) {
|
|
|
|
offset = 0;
|
|
|
|
}
|
2017-11-02 06:48:32 +00:00
|
|
|
}
|
2017-10-11 21:07:32 +00:00
|
|
|
|
2017-11-02 06:48:32 +00:00
|
|
|
refreshDisasm(offset);
|
2017-10-11 21:07:32 +00:00
|
|
|
}
|
|
|
|
|
2017-11-20 10:04:03 +00:00
|
|
|
bool DisassemblyWidget::updateMaxLines()
|
2017-11-02 06:48:32 +00:00
|
|
|
{
|
2017-11-15 21:56:10 +00:00
|
|
|
int currentMaxLines = qhelpers::getMaxFullyDisplayedLines(mDisasTextEdit);
|
2017-11-20 10:04:03 +00:00
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
if (currentMaxLines != maxLines) {
|
2017-11-02 06:48:32 +00:00
|
|
|
maxLines = currentMaxLines;
|
|
|
|
refreshDisasm();
|
2017-11-20 10:04:03 +00:00
|
|
|
return true;
|
2017-11-02 06:48:32 +00:00
|
|
|
}
|
2017-11-20 10:04:03 +00:00
|
|
|
|
|
|
|
return false;
|
2017-11-02 06:48:32 +00:00
|
|
|
}
|
|
|
|
|
2017-10-22 13:55:42 +00:00
|
|
|
void DisassemblyWidget::highlightCurrentLine()
|
2017-10-11 21:07:32 +00:00
|
|
|
{
|
2017-10-22 13:55:42 +00:00
|
|
|
QList<QTextEdit::ExtraSelection> extraSelections;
|
2019-05-23 16:22:31 +00:00
|
|
|
QColor highlightColor = ConfigColor("lineHighlight");
|
2017-12-02 15:03:55 +00:00
|
|
|
|
2017-10-22 13:55:42 +00:00
|
|
|
// Highlight the current word
|
|
|
|
QTextCursor cursor = mDisasTextEdit->textCursor();
|
2020-11-15 20:26:49 +00:00
|
|
|
auto clickedCharPos = cursor.positionInBlock();
|
|
|
|
// Select the line (BlockUnderCursor matches a line with current implementation)
|
|
|
|
cursor.select(QTextCursor::BlockUnderCursor);
|
|
|
|
// Remove any non-breakable space from the current line
|
|
|
|
QString searchString = cursor.selectedText().replace("\xc2\xa0", " ");
|
|
|
|
// Cut the line in "tokens" that can be highlighted
|
|
|
|
static const QRegularExpression tokenRegExp(R"(\b(?<!\.)([^\s]+)\b(?!\.))");
|
|
|
|
QRegularExpressionMatchIterator i = tokenRegExp.globalMatch(searchString);
|
|
|
|
while (i.hasNext()) {
|
|
|
|
QRegularExpressionMatch match = i.next();
|
|
|
|
// Current token is under our cursor, select this one
|
|
|
|
if (match.capturedStart() <= clickedCharPos && match.capturedEnd() > clickedCharPos) {
|
|
|
|
curHighlightedWord = match.captured();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2017-10-11 21:07:32 +00:00
|
|
|
|
2018-09-05 12:47:09 +00:00
|
|
|
// Highlight the current line
|
|
|
|
QTextEdit::ExtraSelection highlightSelection;
|
|
|
|
highlightSelection.cursor = cursor;
|
|
|
|
highlightSelection.cursor.movePosition(QTextCursor::Start);
|
|
|
|
while (true) {
|
|
|
|
RVA lineOffset = readDisassemblyOffset(highlightSelection.cursor);
|
|
|
|
if (lineOffset == seekable->getOffset()) {
|
|
|
|
highlightSelection.format.setBackground(highlightColor);
|
|
|
|
highlightSelection.format.setProperty(QTextFormat::FullWidthSelection, true);
|
|
|
|
highlightSelection.cursor.clearSelection();
|
|
|
|
extraSelections.append(highlightSelection);
|
|
|
|
} else if (lineOffset != RVA_INVALID && lineOffset > seekable->getOffset()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
highlightSelection.cursor.movePosition(QTextCursor::EndOfLine);
|
|
|
|
if (highlightSelection.cursor.atEnd()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
highlightSelection.cursor.movePosition(QTextCursor::Down);
|
|
|
|
}
|
|
|
|
|
2017-12-02 15:03:55 +00:00
|
|
|
// Highlight all the words in the document same as the current one
|
2019-07-12 08:57:07 +00:00
|
|
|
extraSelections.append(createSameWordsSelections(mDisasTextEdit, curHighlightedWord));
|
2017-10-13 13:53:23 +00:00
|
|
|
|
2020-10-13 22:01:04 +00:00
|
|
|
mDisasTextEdit->setExtraSelections(extraSelections);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DisassemblyWidget::highlightPCLine()
|
|
|
|
{
|
2018-06-22 15:57:15 +00:00
|
|
|
RVA PCAddr = Core()->getProgramCounterValue();
|
2020-10-13 22:01:04 +00:00
|
|
|
|
|
|
|
QColor highlightPCColor = ConfigColor("highlightPC");
|
|
|
|
|
|
|
|
QList<QTextEdit::ExtraSelection> pcSelections;
|
|
|
|
QTextEdit::ExtraSelection highlightSelection;
|
|
|
|
highlightSelection.cursor = mDisasTextEdit->textCursor();
|
2018-06-22 15:57:15 +00:00
|
|
|
highlightSelection.cursor.movePosition(QTextCursor::Start);
|
|
|
|
if (PCAddr != RVA_INVALID) {
|
|
|
|
while (true) {
|
|
|
|
RVA lineOffset = readDisassemblyOffset(highlightSelection.cursor);
|
|
|
|
if (lineOffset == PCAddr) {
|
|
|
|
highlightSelection.format.setBackground(highlightPCColor);
|
|
|
|
highlightSelection.format.setProperty(QTextFormat::FullWidthSelection, true);
|
|
|
|
highlightSelection.cursor.clearSelection();
|
2020-10-13 22:01:04 +00:00
|
|
|
pcSelections.append(highlightSelection);
|
2018-06-22 15:57:15 +00:00
|
|
|
} else if (lineOffset != RVA_INVALID && lineOffset > PCAddr) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
highlightSelection.cursor.movePosition(QTextCursor::EndOfLine);
|
|
|
|
if (highlightSelection.cursor.atEnd()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
highlightSelection.cursor.movePosition(QTextCursor::Down);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-13 22:01:04 +00:00
|
|
|
// Don't override any extraSelections already set
|
|
|
|
QList<QTextEdit::ExtraSelection> currentSelections = mDisasTextEdit->extraSelections();
|
|
|
|
currentSelections.append(pcSelections);
|
|
|
|
|
|
|
|
mDisasTextEdit->setExtraSelections(currentSelections);
|
2017-10-22 13:55:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DisassemblyWidget::showDisasContextMenu(const QPoint &pt)
|
|
|
|
{
|
2017-11-03 11:31:20 +00:00
|
|
|
mCtxMenu->exec(mDisasTextEdit->mapToGlobal(pt));
|
2017-10-22 13:55:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
RVA DisassemblyWidget::readCurrentDisassemblyOffset()
|
2017-11-28 11:56:38 +00:00
|
|
|
{
|
|
|
|
QTextCursor tc = mDisasTextEdit->textCursor();
|
|
|
|
return readDisassemblyOffset(tc);
|
|
|
|
}
|
|
|
|
|
|
|
|
RVA DisassemblyWidget::readDisassemblyOffset(QTextCursor tc)
|
2017-10-22 13:55:42 +00:00
|
|
|
{
|
2017-12-16 13:01:58 +00:00
|
|
|
auto userData = getUserData(tc.block());
|
2018-03-21 20:32:32 +00:00
|
|
|
if (!userData) {
|
2017-12-13 14:30:00 +00:00
|
|
|
return RVA_INVALID;
|
2017-10-22 13:55:42 +00:00
|
|
|
}
|
|
|
|
|
2017-12-16 13:01:58 +00:00
|
|
|
return userData->line.offset;
|
2017-10-22 13:55:42 +00:00
|
|
|
}
|
|
|
|
|
2017-11-02 06:48:32 +00:00
|
|
|
void DisassemblyWidget::updateCursorPosition()
|
2017-10-22 13:55:42 +00:00
|
|
|
{
|
2018-05-25 14:30:59 +00:00
|
|
|
RVA offset = seekable->getOffset();
|
2017-10-11 21:07:32 +00:00
|
|
|
|
2017-12-02 13:27:15 +00:00
|
|
|
// already fine where it is?
|
|
|
|
RVA currentLineOffset = readCurrentDisassemblyOffset();
|
2018-03-21 20:32:32 +00:00
|
|
|
if (currentLineOffset == offset) {
|
2017-12-02 13:27:15 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
connectCursorPositionChanged(true);
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
if (offset < topOffset || (offset > bottomOffset && bottomOffset != RVA_INVALID)) {
|
2017-11-02 06:48:32 +00:00
|
|
|
mDisasTextEdit->moveCursor(QTextCursor::Start);
|
2021-01-24 14:50:13 +00:00
|
|
|
mDisasTextEdit->setExtraSelections(
|
|
|
|
createSameWordsSelections(mDisasTextEdit, curHighlightedWord));
|
2018-03-21 20:32:32 +00:00
|
|
|
} else {
|
2017-11-02 06:48:32 +00:00
|
|
|
RVA currentCursorOffset = readCurrentDisassemblyOffset();
|
|
|
|
QTextCursor originalCursor = mDisasTextEdit->textCursor();
|
2017-10-11 21:07:32 +00:00
|
|
|
|
2017-11-02 06:48:32 +00:00
|
|
|
QTextCursor cursor = originalCursor;
|
|
|
|
cursor.movePosition(QTextCursor::Start);
|
2017-10-11 21:07:32 +00:00
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
while (true) {
|
2017-12-02 13:27:15 +00:00
|
|
|
RVA lineOffset = readDisassemblyOffset(cursor);
|
2018-03-21 20:32:32 +00:00
|
|
|
if (lineOffset == offset) {
|
|
|
|
if (cursorLineOffset > 0) {
|
2021-01-24 14:50:13 +00:00
|
|
|
cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor,
|
|
|
|
cursorLineOffset);
|
2017-12-16 13:01:58 +00:00
|
|
|
}
|
2019-04-22 08:48:06 +00:00
|
|
|
if (cursorCharOffset > 0) {
|
|
|
|
cursor.movePosition(QTextCursor::StartOfLine);
|
2021-01-24 14:50:13 +00:00
|
|
|
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor,
|
|
|
|
cursorCharOffset);
|
2019-04-22 08:48:06 +00:00
|
|
|
}
|
2017-12-16 13:01:58 +00:00
|
|
|
|
2017-12-02 13:27:15 +00:00
|
|
|
mDisasTextEdit->setTextCursor(cursor);
|
2017-11-02 06:48:32 +00:00
|
|
|
highlightCurrentLine();
|
|
|
|
break;
|
2018-03-21 20:32:32 +00:00
|
|
|
} else if (lineOffset != RVA_INVALID && lineOffset > offset) {
|
2017-11-02 06:48:32 +00:00
|
|
|
mDisasTextEdit->moveCursor(QTextCursor::Start);
|
|
|
|
mDisasTextEdit->setExtraSelections({});
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
cursor.movePosition(QTextCursor::EndOfLine);
|
2018-03-21 20:32:32 +00:00
|
|
|
if (cursor.atEnd()) {
|
2017-11-02 06:48:32 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
cursor.movePosition(QTextCursor::Down);
|
2017-10-11 21:07:32 +00:00
|
|
|
}
|
2017-11-02 06:48:32 +00:00
|
|
|
|
|
|
|
// this is true if a seek came from the user clicking on a line.
|
|
|
|
// then the cursor should be restored 1:1 to retain selection and cursor position.
|
2018-03-21 20:32:32 +00:00
|
|
|
if (currentCursorOffset == offset) {
|
2017-11-02 06:48:32 +00:00
|
|
|
mDisasTextEdit->setTextCursor(originalCursor);
|
2017-10-11 21:07:32 +00:00
|
|
|
}
|
|
|
|
}
|
2020-10-13 22:01:04 +00:00
|
|
|
|
|
|
|
highlightPCLine();
|
|
|
|
|
2017-11-02 06:48:32 +00:00
|
|
|
connectCursorPositionChanged(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DisassemblyWidget::connectCursorPositionChanged(bool disconnect)
|
|
|
|
{
|
2018-03-21 20:32:32 +00:00
|
|
|
if (disconnect) {
|
2021-01-24 14:50:13 +00:00
|
|
|
QObject::disconnect(mDisasTextEdit, &QPlainTextEdit::cursorPositionChanged, this,
|
|
|
|
&DisassemblyWidget::cursorPositionChanged);
|
2018-03-21 20:32:32 +00:00
|
|
|
} else {
|
2021-01-24 14:50:13 +00:00
|
|
|
connect(mDisasTextEdit, &QPlainTextEdit::cursorPositionChanged, this,
|
|
|
|
&DisassemblyWidget::cursorPositionChanged);
|
2017-11-02 06:48:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void DisassemblyWidget::cursorPositionChanged()
|
|
|
|
{
|
|
|
|
RVA offset = readCurrentDisassemblyOffset();
|
2017-12-16 13:01:58 +00:00
|
|
|
|
|
|
|
cursorLineOffset = 0;
|
|
|
|
QTextCursor c = mDisasTextEdit->textCursor();
|
2019-04-22 08:48:06 +00:00
|
|
|
cursorCharOffset = c.positionInBlock();
|
2018-03-21 20:32:32 +00:00
|
|
|
while (c.blockNumber() > 0) {
|
2017-12-16 13:01:58 +00:00
|
|
|
c.movePosition(QTextCursor::PreviousBlock);
|
2018-03-21 20:32:32 +00:00
|
|
|
if (readDisassemblyOffset(c) != offset) {
|
2017-12-16 13:01:58 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
cursorLineOffset++;
|
|
|
|
}
|
|
|
|
|
|
|
|
seekFromCursor = true;
|
2018-05-25 14:30:59 +00:00
|
|
|
seekable->seek(offset);
|
2017-12-16 13:01:58 +00:00
|
|
|
seekFromCursor = false;
|
2017-12-02 13:27:15 +00:00
|
|
|
highlightCurrentLine();
|
2020-10-13 22:01:04 +00:00
|
|
|
highlightPCLine();
|
2017-12-02 15:43:21 +00:00
|
|
|
mCtxMenu->setCanCopy(mDisasTextEdit->textCursor().hasSelection());
|
2019-03-04 21:45:17 +00:00
|
|
|
if (mDisasTextEdit->textCursor().hasSelection()) {
|
|
|
|
// A word is selected so use it
|
|
|
|
mCtxMenu->setCurHighlightedWord(mDisasTextEdit->textCursor().selectedText());
|
|
|
|
} else {
|
|
|
|
// No word is selected so use the word under the cursor
|
|
|
|
mCtxMenu->setCurHighlightedWord(curHighlightedWord);
|
|
|
|
}
|
2019-06-29 22:26:48 +00:00
|
|
|
leftPanel->update();
|
2017-10-11 21:07:32 +00:00
|
|
|
}
|
|
|
|
|
2017-12-16 13:01:58 +00:00
|
|
|
void DisassemblyWidget::moveCursorRelative(bool up, bool page)
|
|
|
|
{
|
2018-03-21 20:32:32 +00:00
|
|
|
if (page) {
|
2017-12-17 14:11:27 +00:00
|
|
|
RVA offset;
|
2018-03-21 20:32:32 +00:00
|
|
|
if (!up) {
|
2017-12-17 14:11:27 +00:00
|
|
|
offset = Core()->nextOpAddr(bottomOffset, 1);
|
2018-04-15 17:06:05 +00:00
|
|
|
if (offset < bottomOffset) {
|
|
|
|
offset = RVA_MAX;
|
|
|
|
}
|
2018-03-21 20:32:32 +00:00
|
|
|
} else {
|
2017-12-17 14:11:27 +00:00
|
|
|
offset = Core()->prevOpAddr(topOffset, maxLines);
|
2018-04-15 17:06:05 +00:00
|
|
|
if (offset > topOffset) {
|
|
|
|
offset = 0;
|
|
|
|
} else {
|
|
|
|
// disassembly from calculated offset may have more than maxLines lines
|
|
|
|
// move some instructions down if necessary.
|
|
|
|
|
|
|
|
auto lines = Core()->disassembleLines(offset, maxLines).toVector();
|
|
|
|
int oldTopLine;
|
|
|
|
for (oldTopLine = lines.length(); oldTopLine > 0; oldTopLine--) {
|
|
|
|
if (lines[oldTopLine - 1].offset < topOffset) {
|
|
|
|
break;
|
|
|
|
}
|
2017-12-17 14:11:27 +00:00
|
|
|
}
|
2017-12-16 13:01:58 +00:00
|
|
|
|
2018-04-15 17:06:05 +00:00
|
|
|
int overflowLines = oldTopLine - maxLines;
|
|
|
|
if (overflowLines > 0) {
|
|
|
|
while (lines[overflowLines - 1].offset == lines[overflowLines].offset
|
2021-01-24 14:50:13 +00:00
|
|
|
&& overflowLines < lines.length() - 1) {
|
2018-04-15 17:06:05 +00:00
|
|
|
overflowLines++;
|
|
|
|
}
|
|
|
|
offset = lines[overflowLines].offset;
|
2017-12-17 14:11:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
refreshDisasm(offset);
|
2018-03-21 20:32:32 +00:00
|
|
|
} else { // normal arrow keys
|
2017-12-17 14:11:27 +00:00
|
|
|
int blockCount = mDisasTextEdit->blockCount();
|
2018-03-21 20:32:32 +00:00
|
|
|
if (blockCount < 1) {
|
2017-12-17 14:11:27 +00:00
|
|
|
return;
|
|
|
|
}
|
2017-12-16 13:01:58 +00:00
|
|
|
|
2017-12-17 14:11:27 +00:00
|
|
|
int blockNumber = mDisasTextEdit->textCursor().blockNumber();
|
2017-12-16 13:01:58 +00:00
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
if (blockNumber == blockCount - 1 && !up) {
|
2017-12-17 14:11:27 +00:00
|
|
|
scrollInstructions(1);
|
2018-03-21 20:32:32 +00:00
|
|
|
} else if (blockNumber == 0 && up) {
|
2017-12-17 14:11:27 +00:00
|
|
|
scrollInstructions(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
mDisasTextEdit->moveCursor(up ? QTextCursor::Up : QTextCursor::Down);
|
|
|
|
|
|
|
|
// handle cases where top instruction offsets change
|
|
|
|
RVA offset = readCurrentDisassemblyOffset();
|
2018-05-25 14:30:59 +00:00
|
|
|
if (offset != seekable->getOffset()) {
|
|
|
|
seekable->seek(offset);
|
2017-12-17 14:11:27 +00:00
|
|
|
highlightCurrentLine();
|
2020-10-13 22:01:04 +00:00
|
|
|
highlightPCLine();
|
2017-12-17 14:11:27 +00:00
|
|
|
}
|
2017-12-16 13:01:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-12 09:52:42 +00:00
|
|
|
void DisassemblyWidget::jumpToOffsetUnderCursor(const QTextCursor &cursor)
|
|
|
|
{
|
|
|
|
RVA offset = readDisassemblyOffset(cursor);
|
2019-12-14 12:57:36 +00:00
|
|
|
seekable->seekToReference(offset);
|
2019-12-12 09:52:42 +00:00
|
|
|
}
|
|
|
|
|
2017-10-12 19:55:15 +00:00
|
|
|
bool DisassemblyWidget::eventFilter(QObject *obj, QEvent *event)
|
2017-10-11 21:07:32 +00:00
|
|
|
{
|
2019-01-15 06:48:30 +00:00
|
|
|
if (event->type() == QEvent::MouseButtonDblClick
|
|
|
|
&& (obj == mDisasTextEdit || obj == mDisasTextEdit->viewport())) {
|
2017-10-11 21:07:32 +00:00
|
|
|
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
|
2017-11-28 11:56:38 +00:00
|
|
|
|
2021-01-24 14:50:13 +00:00
|
|
|
const QTextCursor &cursor =
|
|
|
|
mDisasTextEdit->cursorForPosition(QPoint(mouseEvent->x(), mouseEvent->y()));
|
2019-12-12 09:52:42 +00:00
|
|
|
jumpToOffsetUnderCursor(cursor);
|
2017-11-28 11:56:38 +00:00
|
|
|
|
2020-12-05 07:26:26 +00:00
|
|
|
return true;
|
2021-01-24 14:50:13 +00:00
|
|
|
} else if (event->type() == QEvent::ToolTip && obj == mDisasTextEdit->viewport()) {
|
|
|
|
QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
|
2020-12-05 07:26:26 +00:00
|
|
|
|
|
|
|
auto cursorForWord = mDisasTextEdit->cursorForPosition(helpEvent->pos());
|
|
|
|
cursorForWord.select(QTextCursor::WordUnderCursor);
|
|
|
|
|
|
|
|
RVA offsetFrom = readDisassemblyOffset(cursorForWord);
|
|
|
|
RVA offsetTo = RVA_INVALID;
|
|
|
|
|
|
|
|
QList<XrefDescription> refs = Core()->getXRefs(offsetFrom, false, false);
|
|
|
|
|
|
|
|
if (refs.length()) {
|
|
|
|
if (refs.length() > 1) {
|
|
|
|
qWarning() << tr("More than one (%1) references here. Weird behaviour expected.")
|
2021-01-24 14:50:13 +00:00
|
|
|
.arg(refs.length());
|
2020-12-05 07:26:26 +00:00
|
|
|
}
|
2021-01-24 14:50:13 +00:00
|
|
|
offsetTo = refs.at(0).to; // This is the offset we want to preview
|
2020-12-05 07:26:26 +00:00
|
|
|
|
2021-01-24 14:50:13 +00:00
|
|
|
if (Q_UNLIKELY(offsetFrom != refs.at(0).from)) {
|
2020-12-05 07:26:26 +00:00
|
|
|
qWarning() << tr("offsetFrom (%1) differs from refs.at(0).from (%(2))")
|
2021-01-24 14:50:13 +00:00
|
|
|
.arg(offsetFrom)
|
|
|
|
.arg(refs.at(0).from);
|
2020-12-05 07:26:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Only if the offset we point *to* is different from the one the cursor is currently
|
|
|
|
// on *and* the former is a valid offset, we are allowed to get a preview of offsetTo
|
2021-01-24 14:50:13 +00:00
|
|
|
if (offsetTo != offsetFrom && offsetTo != RVA_INVALID) {
|
2020-12-05 07:26:26 +00:00
|
|
|
QStringList disasmPreview = Core()->getDisassemblyPreview(offsetTo, 10);
|
|
|
|
|
|
|
|
// Last check to make sure the returned preview isn't an empty text (QStringList)
|
|
|
|
if (!disasmPreview.isEmpty()) {
|
|
|
|
const QFont &fnt = Config()->getFont();
|
2021-01-24 14:50:13 +00:00
|
|
|
QFontMetrics fm { fnt };
|
|
|
|
|
|
|
|
QString tooltip =
|
|
|
|
QString("<html><div style=\"font-family: %1; font-size: %2pt; "
|
|
|
|
"white-space: nowrap;\"><div style=\"margin-bottom: "
|
|
|
|
"10px;\"><strong>Disassembly Preview</strong>:<br>%3<div>")
|
|
|
|
.arg(fnt.family())
|
|
|
|
.arg(qMax(6, fnt.pointSize() - 1))
|
|
|
|
.arg(disasmPreview.join("<br>"));
|
2020-12-05 07:26:26 +00:00
|
|
|
QToolTip::showText(helpEvent->globalPos(), tooltip, this, QRect(), 3500);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-28 11:56:38 +00:00
|
|
|
return true;
|
2019-01-15 06:48:30 +00:00
|
|
|
}
|
2019-12-12 09:52:42 +00:00
|
|
|
|
2019-08-28 12:37:52 +00:00
|
|
|
return MemoryDockWidget::eventFilter(obj, event);
|
2017-10-11 21:07:32 +00:00
|
|
|
}
|
|
|
|
|
2019-12-12 09:52:42 +00:00
|
|
|
void DisassemblyWidget::keyPressEvent(QKeyEvent *event)
|
|
|
|
{
|
2021-01-24 14:50:13 +00:00
|
|
|
if (event->key() == Qt::Key_Return) {
|
2019-12-12 09:52:42 +00:00
|
|
|
const QTextCursor cursor = mDisasTextEdit->textCursor();
|
|
|
|
jumpToOffsetUnderCursor(cursor);
|
|
|
|
}
|
|
|
|
|
|
|
|
MemoryDockWidget::keyPressEvent(event);
|
|
|
|
}
|
|
|
|
|
2019-06-18 13:02:41 +00:00
|
|
|
QString DisassemblyWidget::getWindowTitle() const
|
|
|
|
{
|
|
|
|
return tr("Disassembly");
|
|
|
|
}
|
|
|
|
|
2017-10-12 19:55:15 +00:00
|
|
|
void DisassemblyWidget::on_seekChanged(RVA offset)
|
2017-10-11 21:07:32 +00:00
|
|
|
{
|
2018-03-21 20:32:32 +00:00
|
|
|
if (!seekFromCursor) {
|
2017-12-16 13:01:58 +00:00
|
|
|
cursorLineOffset = 0;
|
2019-04-22 08:48:06 +00:00
|
|
|
cursorCharOffset = 0;
|
2017-12-16 13:01:58 +00:00
|
|
|
}
|
|
|
|
|
2021-01-24 14:50:13 +00:00
|
|
|
if (topOffset != RVA_INVALID && offset >= topOffset && offset <= bottomOffset) {
|
2017-11-02 06:48:32 +00:00
|
|
|
// if the line with the seek offset is currently visible, just move the cursor there
|
|
|
|
updateCursorPosition();
|
2018-03-21 20:32:32 +00:00
|
|
|
} else {
|
2017-11-02 06:48:32 +00:00
|
|
|
// otherwise scroll there
|
|
|
|
refreshDisasm(offset);
|
|
|
|
}
|
2017-11-03 11:31:20 +00:00
|
|
|
mCtxMenu->setOffset(offset);
|
2017-10-11 21:07:32 +00:00
|
|
|
}
|
2017-10-14 09:35:49 +00:00
|
|
|
|
|
|
|
void DisassemblyWidget::fontsUpdatedSlot()
|
|
|
|
{
|
2017-11-30 14:00:22 +00:00
|
|
|
setupFonts();
|
2017-11-20 10:04:03 +00:00
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
if (!updateMaxLines()) { // updateMaxLines() returns true if it already refreshed.
|
2017-11-20 10:04:03 +00:00
|
|
|
refreshDisasm();
|
|
|
|
}
|
2017-10-14 09:35:49 +00:00
|
|
|
}
|
2017-10-16 19:00:47 +00:00
|
|
|
|
2017-11-20 11:23:37 +00:00
|
|
|
void DisassemblyWidget::colorsUpdatedSlot()
|
|
|
|
{
|
2017-11-30 14:00:22 +00:00
|
|
|
setupColors();
|
2017-11-20 11:23:37 +00:00
|
|
|
refreshDisasm();
|
|
|
|
}
|
|
|
|
|
2017-11-30 14:00:22 +00:00
|
|
|
void DisassemblyWidget::setupFonts()
|
|
|
|
{
|
|
|
|
mDisasTextEdit->setFont(Config()->getFont());
|
|
|
|
}
|
|
|
|
|
|
|
|
void DisassemblyWidget::setupColors()
|
|
|
|
{
|
|
|
|
mDisasTextEdit->setStyleSheet(QString("QPlainTextEdit { background-color: %1; color: %2; }")
|
2021-01-24 14:50:13 +00:00
|
|
|
.arg(ConfigColor("gui.background").name())
|
|
|
|
.arg(ConfigColor("btext").name()));
|
2017-11-30 14:00:22 +00:00
|
|
|
}
|
|
|
|
|
2021-01-24 14:50:13 +00:00
|
|
|
DisassemblyScrollArea::DisassemblyScrollArea(QWidget *parent) : QAbstractScrollArea(parent) {}
|
2017-11-02 06:48:32 +00:00
|
|
|
|
|
|
|
bool DisassemblyScrollArea::viewportEvent(QEvent *event)
|
|
|
|
{
|
|
|
|
int dy = verticalScrollBar()->value() - 5;
|
2018-03-21 20:32:32 +00:00
|
|
|
if (dy != 0) {
|
2017-11-02 06:48:32 +00:00
|
|
|
emit scrollLines(dy);
|
|
|
|
}
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
if (event->type() == QEvent::Resize) {
|
2017-11-02 06:48:32 +00:00
|
|
|
emit disassemblyResized();
|
|
|
|
}
|
|
|
|
|
|
|
|
resetScrollBars();
|
|
|
|
return QAbstractScrollArea::viewportEvent(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DisassemblyScrollArea::resetScrollBars()
|
|
|
|
{
|
|
|
|
verticalScrollBar()->blockSignals(true);
|
|
|
|
verticalScrollBar()->setRange(0, 10);
|
|
|
|
verticalScrollBar()->setValue(5);
|
|
|
|
verticalScrollBar()->blockSignals(false);
|
|
|
|
}
|
|
|
|
|
2019-07-20 13:10:49 +00:00
|
|
|
qreal DisassemblyTextEdit::textOffset() const
|
|
|
|
{
|
2021-01-24 14:50:13 +00:00
|
|
|
return (blockBoundingGeometry(document()->begin()).topLeft() + contentOffset()).y();
|
2019-07-20 13:10:49 +00:00
|
|
|
}
|
|
|
|
|
2017-11-02 06:48:32 +00:00
|
|
|
bool DisassemblyTextEdit::viewportEvent(QEvent *event)
|
|
|
|
{
|
2018-03-21 20:32:32 +00:00
|
|
|
switch (event->type()) {
|
|
|
|
case QEvent::Type::Wheel:
|
|
|
|
return false;
|
|
|
|
default:
|
|
|
|
return QAbstractScrollArea::viewportEvent(event);
|
2017-11-02 06:48:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void DisassemblyTextEdit::scrollContentsBy(int dx, int dy)
|
|
|
|
{
|
2018-03-21 20:32:32 +00:00
|
|
|
if (!lockScroll) {
|
2017-11-02 06:48:32 +00:00
|
|
|
QPlainTextEdit::scrollContentsBy(dx, dy);
|
|
|
|
}
|
|
|
|
}
|
2017-11-04 11:46:29 +00:00
|
|
|
|
2018-10-10 11:33:55 +00:00
|
|
|
void DisassemblyTextEdit::keyPressEvent(QKeyEvent *event)
|
2017-11-04 11:46:29 +00:00
|
|
|
{
|
2018-10-10 11:33:55 +00:00
|
|
|
Q_UNUSED(event)
|
2021-01-24 14:50:13 +00:00
|
|
|
// QPlainTextEdit::keyPressEvent(event);
|
2017-11-04 11:46:29 +00:00
|
|
|
}
|
2017-11-28 15:42:40 +00:00
|
|
|
|
2017-12-02 13:10:15 +00:00
|
|
|
void DisassemblyTextEdit::mousePressEvent(QMouseEvent *event)
|
|
|
|
{
|
|
|
|
QPlainTextEdit::mousePressEvent(event);
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
if (event->button() == Qt::RightButton && !textCursor().hasSelection()) {
|
2017-12-02 13:10:15 +00:00
|
|
|
setTextCursor(cursorForPosition(event->pos()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-28 15:42:40 +00:00
|
|
|
void DisassemblyWidget::seekPrev()
|
|
|
|
{
|
|
|
|
Core()->seekPrev();
|
|
|
|
}
|
2019-06-26 07:12:39 +00:00
|
|
|
|
|
|
|
/*********************
|
|
|
|
* Left panel
|
|
|
|
*********************/
|
2019-06-29 22:26:48 +00:00
|
|
|
|
2019-06-26 07:12:39 +00:00
|
|
|
DisassemblyLeftPanel::DisassemblyLeftPanel(DisassemblyWidget *disas)
|
|
|
|
{
|
|
|
|
this->disas = disas;
|
2021-02-15 06:46:57 +00:00
|
|
|
arrows.reserve((arrowsSize * 3) / 2);
|
2019-06-26 07:12:39 +00:00
|
|
|
}
|
|
|
|
|
2021-01-24 14:50:13 +00:00
|
|
|
void DisassemblyLeftPanel::wheelEvent(QWheelEvent *event)
|
|
|
|
{
|
2019-07-14 00:52:43 +00:00
|
|
|
int count = -(event->angleDelta() / 15).y();
|
|
|
|
count -= (count > 0 ? 5 : -5);
|
|
|
|
|
|
|
|
this->disas->scrollInstructions(count);
|
|
|
|
}
|
|
|
|
|
2019-06-26 07:12:39 +00:00
|
|
|
void DisassemblyLeftPanel::paintEvent(QPaintEvent *event)
|
|
|
|
{
|
2019-06-29 22:26:48 +00:00
|
|
|
Q_UNUSED(event)
|
2019-06-26 07:12:39 +00:00
|
|
|
|
2019-07-06 09:08:48 +00:00
|
|
|
constexpr int penSizePix = 1;
|
2019-06-29 22:26:48 +00:00
|
|
|
constexpr int distanceBetweenLines = 10;
|
|
|
|
constexpr int arrowWidth = 5;
|
2019-06-26 07:12:39 +00:00
|
|
|
int rightOffset = size().rwidth();
|
2021-01-24 14:50:13 +00:00
|
|
|
auto tEdit = qobject_cast<DisassemblyTextEdit *>(disas->getTextWidget());
|
2019-07-20 13:10:49 +00:00
|
|
|
int topOffset = int(tEdit->contentsMargins().top() + tEdit->textOffset());
|
2019-06-26 07:12:39 +00:00
|
|
|
int lineHeight = disas->getFontMetrics().height();
|
2019-06-29 22:26:48 +00:00
|
|
|
QColor arrowColorDown = ConfigColor("flow");
|
|
|
|
QColor arrowColorUp = ConfigColor("cflow");
|
2019-06-26 07:12:39 +00:00
|
|
|
QPainter p(this);
|
2019-06-29 22:26:48 +00:00
|
|
|
QPen penDown(arrowColorDown, penSizePix, Qt::SolidLine, Qt::FlatCap, Qt::RoundJoin);
|
|
|
|
QPen penUp(arrowColorUp, penSizePix, Qt::SolidLine, Qt::FlatCap, Qt::RoundJoin);
|
|
|
|
// Fill background
|
|
|
|
p.fillRect(event->rect(), Config()->getColor("gui.background").darker(115));
|
2019-06-26 07:12:39 +00:00
|
|
|
|
|
|
|
QList<DisassemblyLine> lines = disas->getLines();
|
|
|
|
|
2021-02-15 06:46:57 +00:00
|
|
|
using LineInfo = std::pair<RVA, int>;
|
|
|
|
std::vector<LineInfo> lineOffsets;
|
|
|
|
lineOffsets.reserve(lines.size());
|
|
|
|
|
|
|
|
for (int i = 0; i < lines.size(); i++) {
|
|
|
|
lineOffsets.emplace_back(lines[i].offset, i);
|
|
|
|
if (lines[i].arrow != RVA_INVALID) {
|
|
|
|
Arrow a { lines[i].offset, lines[i].arrow };
|
|
|
|
bool contains = std::find_if(std::begin(arrows), std::end(arrows), [&](const Arrow& it) {
|
|
|
|
return it.min == a.min && it.max == a.max;
|
|
|
|
}) != std::end(arrows);
|
|
|
|
if (!contains) {
|
|
|
|
arrows.emplace_back(lines[i].offset, lines[i].arrow);
|
|
|
|
}
|
2019-06-29 22:26:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-15 06:46:57 +00:00
|
|
|
auto offsetToLine = [&](RVA offset) -> int {
|
|
|
|
// binary search because linesPixPosition is sorted by offset
|
|
|
|
if (lineOffsets.empty()) {
|
|
|
|
return 0;
|
2019-06-29 22:26:48 +00:00
|
|
|
}
|
2021-02-15 06:46:57 +00:00
|
|
|
if (offset < lineOffsets[0].first) {
|
|
|
|
return -2;
|
|
|
|
}
|
|
|
|
auto res = lower_bound(std::begin(lineOffsets), std::end(lineOffsets), offset, [](const LineInfo& it, RVA offset) {
|
|
|
|
return it.first < offset;
|
|
|
|
});
|
|
|
|
if (res == std::end(lineOffsets)) {
|
|
|
|
return lines.size() + 2;
|
|
|
|
}
|
|
|
|
return res->second;
|
|
|
|
};
|
2019-06-29 22:26:48 +00:00
|
|
|
|
2021-02-15 06:46:57 +00:00
|
|
|
|
|
|
|
RVA visibleTop = lineOffsets[0].first, visibleBottom = lineOffsets.back().first;
|
|
|
|
auto fitsInScreen = [&](const Arrow &a) {
|
|
|
|
return visibleBottom - visibleTop < a.length();
|
|
|
|
};
|
|
|
|
|
|
|
|
std::sort(std::begin(arrows), std::end(arrows), [&](const Arrow& l, const Arrow& r) {
|
|
|
|
int lScreen = fitsInScreen(l), rScreen = fitsInScreen(r);
|
|
|
|
if (lScreen != rScreen) {
|
|
|
|
return lScreen < rScreen;
|
2019-06-29 22:26:48 +00:00
|
|
|
}
|
2021-02-15 06:46:57 +00:00
|
|
|
return l.max != r.max ? l.max < r.max : l.min > r.min;
|
|
|
|
});
|
|
|
|
|
|
|
|
RVA max = 0;
|
|
|
|
RVA min = RVA_MAX;
|
|
|
|
for (auto& it : arrows) {
|
|
|
|
min = std::min(it.min, min);
|
|
|
|
max = std::max(it.max, max);
|
|
|
|
it.level = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t maxLevel = 0;
|
|
|
|
if (!arrows.empty()) {
|
|
|
|
MinMaxAccumulateTree<uint32_t> maxLevelTree(max - min + 2);
|
|
|
|
for (Arrow &arrow : arrows) {
|
|
|
|
RVA top = arrow.min >= min ? arrow.min - min + 1: 0;
|
|
|
|
RVA bottom = std::min(arrow.max - min, max - min) + 2;
|
|
|
|
auto minMax = maxLevelTree.rangeMinMax(top, bottom);
|
|
|
|
if (minMax.first > 1) {
|
|
|
|
arrow.level = 1;
|
|
|
|
} else {
|
|
|
|
arrow.level = minMax.second + 1;
|
|
|
|
maxLevel = std::max(maxLevel, arrow.level);
|
|
|
|
}
|
|
|
|
maxLevelTree.updateRange(top, bottom, arrow.level);
|
2019-06-29 22:26:48 +00:00
|
|
|
}
|
2019-06-26 07:12:39 +00:00
|
|
|
}
|
|
|
|
|
2019-06-29 22:26:48 +00:00
|
|
|
const RVA currOffset = disas->getSeekable()->getOffset();
|
2021-02-15 06:46:57 +00:00
|
|
|
const qreal pixelRatio = qhelpers::devicePixelRatio(p.device());
|
|
|
|
const Arrow visibleRange { lines.first().offset, lines.last().offset };
|
2019-06-26 07:12:39 +00:00
|
|
|
// Draw the lines
|
2021-02-15 06:46:57 +00:00
|
|
|
for (const auto& arrow : arrows) {
|
|
|
|
if (!visibleRange.intersects(arrow)) {
|
2019-06-26 07:12:39 +00:00
|
|
|
continue;
|
|
|
|
}
|
2021-02-15 06:46:57 +00:00
|
|
|
int lineOffset = int((distanceBetweenLines * arrow.level + distanceBetweenLines) * pixelRatio);
|
|
|
|
|
|
|
|
p.setPen(arrow.up ? penUp : penDown);
|
|
|
|
if (arrow.min == currOffset || arrow.max == currOffset) {
|
2019-06-29 22:26:48 +00:00
|
|
|
QPen pen = p.pen();
|
2019-07-30 18:35:00 +00:00
|
|
|
pen.setWidthF((penSizePix * 3) / 2.0);
|
2019-06-29 22:26:48 +00:00
|
|
|
p.setPen(pen);
|
2019-06-26 07:12:39 +00:00
|
|
|
}
|
2019-06-29 22:26:48 +00:00
|
|
|
|
2021-02-15 06:46:57 +00:00
|
|
|
auto lineToPixels = [&] (int i) {
|
|
|
|
int offset = int(arrow.up ? std::floor(pixelRatio) : -std::floor(pixelRatio));
|
|
|
|
return i * lineHeight + lineHeight / 2 + topOffset + offset;
|
|
|
|
};
|
2019-06-26 07:12:39 +00:00
|
|
|
|
2021-02-15 06:46:57 +00:00
|
|
|
int lineStartNumber = offsetToLine(arrow.jmpFromOffset());
|
|
|
|
int currentLineYPos = lineToPixels(lineStartNumber);
|
|
|
|
|
|
|
|
int arrowLineNumber = offsetToLine(arrow.jmpToffset());
|
|
|
|
int lineArrowY = lineToPixels(arrowLineNumber);
|
2019-06-26 07:12:39 +00:00
|
|
|
|
|
|
|
// Draw the lines
|
2021-02-15 06:46:57 +00:00
|
|
|
p.drawLine(rightOffset, currentLineYPos, rightOffset - lineOffset, currentLineYPos); // left
|
|
|
|
p.drawLine(rightOffset - lineOffset, currentLineYPos, rightOffset - lineOffset, lineArrowY); // horizontal
|
2019-06-26 07:12:39 +00:00
|
|
|
|
2021-02-15 06:46:57 +00:00
|
|
|
p.drawLine(rightOffset - lineOffset, lineArrowY, rightOffset, lineArrowY); // right
|
2019-06-26 07:12:39 +00:00
|
|
|
|
2021-02-15 06:46:57 +00:00
|
|
|
{ // triangle
|
2019-06-29 22:26:48 +00:00
|
|
|
QPainterPath arrow;
|
|
|
|
arrow.moveTo(rightOffset - arrowWidth, lineArrowY + arrowWidth);
|
|
|
|
arrow.lineTo(rightOffset - arrowWidth, lineArrowY - arrowWidth);
|
|
|
|
arrow.lineTo(rightOffset, lineArrowY);
|
|
|
|
p.fillPath(arrow, p.pen().brush());
|
2019-06-26 07:12:39 +00:00
|
|
|
}
|
|
|
|
}
|
2021-02-15 06:46:57 +00:00
|
|
|
|
|
|
|
if (maxLevel > maxLevelBeforeFlush) {
|
|
|
|
arrows.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
const size_t eraseN = arrows.size() > arrowsSize ? arrows.size() - arrowsSize: 0;
|
|
|
|
if (eraseN > 0) {
|
|
|
|
const bool scrolledDown = lastBeginOffset > lines.first().offset;
|
|
|
|
std::sort(std::begin(arrows), std::end(arrows), [&](const Arrow& l, const Arrow& r) {
|
|
|
|
if (scrolledDown) {
|
|
|
|
return l.jmpFromOffset() < r.jmpFromOffset();
|
|
|
|
} else {
|
|
|
|
return l.jmpFromOffset() > r.jmpFromOffset();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
arrows.erase(std::end(arrows) - eraseN, std::end(arrows));
|
|
|
|
}
|
|
|
|
|
|
|
|
lastBeginOffset = lines.first().offset;
|
2019-06-26 07:12:39 +00:00
|
|
|
}
|