mirror of https://github.com/PCSX2/pcsx2.git
740 lines
22 KiB
C++
740 lines
22 KiB
C++
/* PCSX2 - PS2 Emulator for PCs
|
|
* Copyright (C) 2002-2022 PCSX2 Dev Team
|
|
*
|
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
* PURPOSE. See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with PCSX2.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "PrecompiledHeader.h"
|
|
|
|
#include "DisassemblyWidget.h"
|
|
|
|
#include "DebugTools/DebugInterface.h"
|
|
#include "DebugTools/DisassemblyManager.h"
|
|
#include "DebugTools/Breakpoints.h"
|
|
#include "DebugTools/MipsAssembler.h"
|
|
|
|
#include "QtUtils.h"
|
|
#include "QtHost.h"
|
|
#include <QtGui/QMouseEvent>
|
|
#include <QtWidgets/QMenu>
|
|
#include <QtGui/QClipboard>
|
|
#include <QtWidgets/QInputDialog>
|
|
#include <QtWidgets/QMessageBox>
|
|
|
|
using namespace QtUtils;
|
|
|
|
DisassemblyWidget::DisassemblyWidget(QWidget* parent)
|
|
: QWidget(parent)
|
|
{
|
|
ui.setupUi(this);
|
|
|
|
CreateCustomContextMenu();
|
|
connect(this, &DisassemblyWidget::customContextMenuRequested, this, &DisassemblyWidget::customMenuRequested);
|
|
}
|
|
|
|
DisassemblyWidget::~DisassemblyWidget() = default;
|
|
|
|
void DisassemblyWidget::CreateCustomContextMenu()
|
|
{
|
|
if (m_contextMenu)
|
|
return; // ???
|
|
m_contextMenu = new QMenu(this);
|
|
|
|
QAction* action = 0;
|
|
m_contextMenu->addAction(action = new QAction(tr("Copy Address"), this));
|
|
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextCopyAddress);
|
|
m_contextMenu->addAction(action = new QAction(tr("Copy Instruction Hex"), this));
|
|
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextCopyInstructionHex);
|
|
m_contextMenu->addAction(action = new QAction(tr("Copy Instruction Text"), this));
|
|
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextCopyInstructionText);
|
|
// TODO: Disassemble to file. Do people use that?
|
|
m_contextMenu->addSeparator();
|
|
m_contextMenu->addAction(action = new QAction(tr("Assemble new Instruction(s)"), this));
|
|
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextAssembleInstruction);
|
|
m_contextMenu->addAction(action = new QAction(tr("NOP Instruction(s)"), this));
|
|
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextNoopInstruction);
|
|
m_contextMenu->addSeparator();
|
|
m_contextMenu->addAction(action = new QAction(tr("Run to Cursor"), this));
|
|
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRunToCursor);
|
|
m_contextMenu->addAction(action = new QAction(tr("Jump to Cursor"), this));
|
|
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextJumpToCursor);
|
|
m_contextMenu->addAction(action = new QAction(tr("Toggle Breakpoint"), this));
|
|
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextToggleBreakpoint);
|
|
m_contextMenu->addAction(action = new QAction(tr("Follow Branch"), this));
|
|
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextFollowBranch);
|
|
m_contextMenu->addSeparator();
|
|
m_contextMenu->addAction(action = new QAction(tr("Go to Address"), this));
|
|
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextGoToAddress);
|
|
m_contextMenu->addAction(action = new QAction(tr("Go to in Memory View"), this));
|
|
connect(action, &QAction::triggered, this, [this]() { gotoInMemory(m_selectedAddressStart); });
|
|
m_contextMenu->addSeparator();
|
|
m_contextMenu->addAction(action = new QAction(tr("Add Function"), this));
|
|
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextAddFunction);
|
|
m_contextMenu->addAction(action = new QAction(tr("Rename Function"), this));
|
|
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRenameFunction);
|
|
m_contextMenu->addAction(action = new QAction(tr("Remove Function"), this));
|
|
connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRemoveFunction);
|
|
}
|
|
|
|
void DisassemblyWidget::contextCopyAddress()
|
|
{
|
|
QGuiApplication::clipboard()->setText(FetchSelectionInfo(SelectionInfo::ADDRESS));
|
|
}
|
|
|
|
void DisassemblyWidget::contextCopyInstructionHex()
|
|
{
|
|
QGuiApplication::clipboard()->setText(FetchSelectionInfo(SelectionInfo::INSTRUCTIONHEX));
|
|
}
|
|
|
|
void DisassemblyWidget::contextCopyInstructionText()
|
|
{
|
|
QGuiApplication::clipboard()->setText(FetchSelectionInfo(SelectionInfo::INSTRUCTIONTEXT));
|
|
}
|
|
|
|
void DisassemblyWidget::contextAssembleInstruction()
|
|
{
|
|
if (!m_cpu->isCpuPaused())
|
|
{
|
|
QMessageBox::warning(this, tr("Assemble Error"), tr("Unable to change assembly while core is running"));
|
|
return;
|
|
}
|
|
|
|
DisassemblyLineInfo line;
|
|
bool ok;
|
|
m_disassemblyManager.getLine(m_selectedAddressStart, false, line);
|
|
QString instruction = QInputDialog::getText(this, tr("Assemble Instruction"), "",
|
|
QLineEdit::Normal, QString("%1 %2").arg(line.name.c_str()).arg(line.params.c_str()), &ok);
|
|
|
|
if (!ok)
|
|
return;
|
|
|
|
u32 encodedInstruction;
|
|
std::string errorText;
|
|
bool valid = MipsAssembleOpcode(instruction.toLocal8Bit().constData(), m_cpu, m_selectedAddressStart, encodedInstruction, errorText);
|
|
|
|
if (!valid)
|
|
{
|
|
QMessageBox::warning(this, tr("Assemble Error"), QString::fromStdString(errorText));
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
Host::RunOnCPUThread([this, start = m_selectedAddressStart, end = m_selectedAddressEnd, cpu = m_cpu, val = encodedInstruction] {
|
|
for (u32 i = start; i <= end; i += 4)
|
|
{
|
|
cpu->write32(i, val);
|
|
}
|
|
QtHost::RunOnUIThread([this] { VMUpdate(); });
|
|
});
|
|
}
|
|
}
|
|
|
|
void DisassemblyWidget::contextNoopInstruction()
|
|
{
|
|
Host::RunOnCPUThread([this, start = m_selectedAddressStart, end = m_selectedAddressEnd, cpu = m_cpu] {
|
|
for (u32 i = start; i <= end; i += 4)
|
|
{
|
|
cpu->write32(i, 0x00);
|
|
}
|
|
QtHost::RunOnUIThread([this] { VMUpdate(); });
|
|
});
|
|
}
|
|
|
|
void DisassemblyWidget::contextRunToCursor()
|
|
{
|
|
Host::RunOnCPUThread([&] {
|
|
CBreakPoints::AddBreakPoint(m_cpu->getCpuType(), m_selectedAddressStart, true);
|
|
m_cpu->resumeCpu();
|
|
});
|
|
}
|
|
|
|
void DisassemblyWidget::contextJumpToCursor()
|
|
{
|
|
m_cpu->setPc(m_selectedAddressStart);
|
|
this->repaint();
|
|
}
|
|
|
|
void DisassemblyWidget::contextToggleBreakpoint()
|
|
{
|
|
if (!m_cpu->isAlive())
|
|
return;
|
|
|
|
if (CBreakPoints::IsAddressBreakPoint(m_cpu->getCpuType(), m_selectedAddressStart))
|
|
{
|
|
Host::RunOnCPUThread([&] { CBreakPoints::RemoveBreakPoint(m_cpu->getCpuType(), m_selectedAddressStart); });
|
|
}
|
|
else
|
|
{
|
|
Host::RunOnCPUThread([&] { CBreakPoints::AddBreakPoint(m_cpu->getCpuType(), m_selectedAddressStart); });
|
|
}
|
|
|
|
breakpointsChanged();
|
|
this->repaint();
|
|
}
|
|
|
|
void DisassemblyWidget::contextFollowBranch()
|
|
{
|
|
DisassemblyLineInfo line;
|
|
|
|
m_disassemblyManager.getLine(m_selectedAddressStart, true, line);
|
|
|
|
if (line.type == DISTYPE_OPCODE || line.type == DISTYPE_MACRO)
|
|
{
|
|
if (line.info.isBranch)
|
|
gotoAddress(line.info.branchTarget);
|
|
else if (line.info.hasRelevantAddress)
|
|
gotoAddress(line.info.releventAddress);
|
|
}
|
|
}
|
|
|
|
void DisassemblyWidget::contextGoToAddress()
|
|
{
|
|
bool ok;
|
|
const QString targetString = QInputDialog::getText(this, tr("Go to address"), "",
|
|
QLineEdit::Normal, "", &ok);
|
|
|
|
if (!ok)
|
|
return;
|
|
|
|
const u32 targetAddress = targetString.toUInt(&ok, 16) & ~3;
|
|
|
|
if (!ok)
|
|
{
|
|
QMessageBox::warning(this, tr("Go to address error"), tr("Invalid address"));
|
|
return;
|
|
}
|
|
|
|
gotoAddress(targetAddress);
|
|
}
|
|
|
|
void DisassemblyWidget::contextAddFunction()
|
|
{
|
|
// Get current function
|
|
const u32 curAddress = m_selectedAddressStart;
|
|
const u32 curFuncAddr = m_cpu->GetSymbolMap().GetFunctionStart(m_selectedAddressStart);
|
|
QString optionaldlgText;
|
|
|
|
if (curFuncAddr != SymbolMap::INVALID_ADDRESS)
|
|
{
|
|
if (curFuncAddr == curAddress) // There is already a function here
|
|
{
|
|
QMessageBox::warning(this, tr("Add Function Error"), tr("A function entry point already exists here. Consider renaming instead."));
|
|
}
|
|
else
|
|
{
|
|
const u32 prevSize = m_cpu->GetSymbolMap().GetFunctionSize(curFuncAddr);
|
|
u32 newSize = curAddress - curFuncAddr;
|
|
|
|
bool ok;
|
|
QString funcName = QInputDialog::getText(this, tr("Add Function"),
|
|
tr("Function will be (0x%1) instructions long.\nEnter function name").arg(prevSize - newSize, 0, 16), QLineEdit::Normal, "", &ok);
|
|
if (!ok)
|
|
return;
|
|
|
|
m_cpu->GetSymbolMap().SetFunctionSize(curFuncAddr, newSize); // End the current function to where we selected
|
|
newSize = prevSize - newSize;
|
|
m_cpu->GetSymbolMap().AddFunction(funcName.toLocal8Bit().constData(), curAddress, newSize);
|
|
m_cpu->GetSymbolMap().SortSymbols();
|
|
m_cpu->GetSymbolMap().UpdateActiveSymbols();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool ok;
|
|
QString funcName = QInputDialog::getText(this, "Add Function",
|
|
tr("Function will be (0x%1) instructions long.\nEnter function name").arg(m_selectedAddressEnd + 4 - m_selectedAddressStart, 0, 16), QLineEdit::Normal, "", &ok);
|
|
if (!ok)
|
|
return;
|
|
|
|
m_cpu->GetSymbolMap().AddFunction(funcName.toLocal8Bit().constData(), m_selectedAddressStart, m_selectedAddressEnd + 4 - m_selectedAddressStart);
|
|
m_cpu->GetSymbolMap().SortSymbols();
|
|
m_cpu->GetSymbolMap().UpdateActiveSymbols();
|
|
}
|
|
}
|
|
|
|
void DisassemblyWidget::contextRemoveFunction()
|
|
{
|
|
u32 curFuncAddr = m_cpu->GetSymbolMap().GetFunctionStart(m_selectedAddressStart);
|
|
|
|
if (curFuncAddr != SymbolMap::INVALID_ADDRESS)
|
|
{
|
|
u32 previousFuncAddr = m_cpu->GetSymbolMap().GetFunctionStart(curFuncAddr - 4);
|
|
if (previousFuncAddr != SymbolMap::INVALID_ADDRESS)
|
|
{
|
|
// Extend the previous function to replace the spot of the function that is going to be removed
|
|
u32 expandedSize = m_cpu->GetSymbolMap().GetFunctionSize(previousFuncAddr) + m_cpu->GetSymbolMap().GetFunctionSize(curFuncAddr);
|
|
m_cpu->GetSymbolMap().SetFunctionSize(previousFuncAddr, expandedSize);
|
|
}
|
|
|
|
m_cpu->GetSymbolMap().RemoveFunction(curFuncAddr, true);
|
|
m_cpu->GetSymbolMap().SortSymbols();
|
|
m_cpu->GetSymbolMap().UpdateActiveSymbols();
|
|
}
|
|
}
|
|
|
|
void DisassemblyWidget::contextRenameFunction()
|
|
{
|
|
const u32 curFuncAddress = m_cpu->GetSymbolMap().GetFunctionStart(m_selectedAddressStart);
|
|
if (curFuncAddress != SymbolMap::INVALID_ADDRESS)
|
|
{
|
|
bool ok;
|
|
QString funcName = QInputDialog::getText(this, tr("Rename Function"), tr("Function name"), QLineEdit::Normal, m_cpu->GetSymbolMap().GetLabelString(curFuncAddress).c_str(), &ok);
|
|
if (!ok)
|
|
return;
|
|
|
|
if (funcName.isEmpty())
|
|
{
|
|
QMessageBox::warning(this, tr("Rename Function Error"), tr("Function name cannot be nothing."));
|
|
}
|
|
else
|
|
{
|
|
m_cpu->GetSymbolMap().SetLabelName(funcName.toLocal8Bit().constData(), curFuncAddress);
|
|
m_cpu->GetSymbolMap().SortSymbols();
|
|
m_cpu->GetSymbolMap().UpdateActiveSymbols();
|
|
this->repaint();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QMessageBox::warning(this, tr("Rename Function Error"), tr("No function / symbol is currently selected."));
|
|
}
|
|
}
|
|
|
|
void DisassemblyWidget::SetCpu(DebugInterface* cpu)
|
|
{
|
|
m_cpu = cpu;
|
|
m_disassemblyManager.setCpu(cpu);
|
|
}
|
|
|
|
QString DisassemblyWidget::GetLineDisasm(u32 address)
|
|
{
|
|
DisassemblyLineInfo lineInfo;
|
|
m_disassemblyManager.getLine(address, true, lineInfo);
|
|
return QString("%1 %2").arg(lineInfo.name.c_str()).arg(lineInfo.params.c_str());
|
|
};
|
|
|
|
// Here we go!
|
|
void DisassemblyWidget::paintEvent(QPaintEvent* event)
|
|
{
|
|
QPainter painter(this);
|
|
|
|
const u32 w = painter.device()->width() - 1;
|
|
const u32 h = painter.device()->height() - 1;
|
|
|
|
// Get the current font size
|
|
const QFontMetrics fm = painter.fontMetrics();
|
|
|
|
// Get the row height
|
|
m_rowHeight = fm.height() + 2;
|
|
|
|
// Find the amount of visible rows
|
|
m_visibleRows = h / m_rowHeight;
|
|
|
|
m_disassemblyManager.analyze(m_visibleStart, m_disassemblyManager.getNthNextAddress(m_visibleStart, m_visibleRows) - m_visibleStart);
|
|
|
|
// Draw the rows
|
|
bool inSelectionBlock = false;
|
|
bool alternate = m_visibleStart % 8;
|
|
|
|
const u32 curPC = m_cpu->getPC(); // Get the PC here, because it'll change when we are drawing and make it seem like there are two PCs
|
|
|
|
for (u32 i = 0; i <= m_visibleRows; i++)
|
|
{
|
|
const u32 rowAddress = (i * 4) + m_visibleStart;
|
|
// Row backgrounds
|
|
|
|
if (inSelectionBlock || (m_selectedAddressStart <= rowAddress && rowAddress <= m_selectedAddressEnd))
|
|
{
|
|
painter.fillRect(0, i * m_rowHeight, w, m_rowHeight, this->palette().highlight());
|
|
inSelectionBlock = m_selectedAddressEnd != rowAddress;
|
|
}
|
|
else
|
|
{
|
|
painter.fillRect(0, i * m_rowHeight, w, m_rowHeight, alternate ? this->palette().base() : this->palette().alternateBase());
|
|
}
|
|
|
|
// Row text
|
|
painter.setPen(GetAddressFunctionColor(rowAddress));
|
|
QString lineString = DisassemblyStringFromAddress(rowAddress, painter.font(), curPC);
|
|
|
|
painter.drawText(2, i * m_rowHeight, w, m_rowHeight, Qt::AlignLeft, lineString);
|
|
alternate = !alternate;
|
|
}
|
|
// Draw the branch lines
|
|
// This is where it gets a little scary
|
|
// It's been mostly copied from the wx implementation
|
|
|
|
u32 visibleEnd = m_disassemblyManager.getNthNextAddress(m_visibleStart, m_visibleRows);
|
|
std::vector<BranchLine> branchLines = m_disassemblyManager.getBranchLines(m_visibleStart, visibleEnd - m_visibleStart);
|
|
|
|
s32 branchCount = 0;
|
|
s32 skippedBranches = 0;
|
|
for (const auto& branchLine : branchLines)
|
|
{
|
|
if (branchCount == 5)
|
|
break;
|
|
const int winBottom = this->height();
|
|
|
|
const int x = this->width() - 10 - ((std::max(0, branchLine.laneIndex - skippedBranches)) * 10);
|
|
|
|
int top, bottom;
|
|
// If the start is technically 'above' our address view
|
|
if (branchLine.first < m_visibleStart)
|
|
{
|
|
top = -1;
|
|
}
|
|
// If the start is technically 'below' our address view
|
|
else if (branchLine.first >= visibleEnd)
|
|
{
|
|
top = winBottom + 1;
|
|
}
|
|
else
|
|
{
|
|
// Explaination
|
|
// ((branchLine.first - m_visibleStart) -> Find the amount of bytes from the top of the view
|
|
// / 4 -> Convert that into rowss in instructions
|
|
// * m_rowHeight -> convert that into rows in pixels
|
|
// + (m_rowHeight / 2) -> Add half a row in pixels to center the arrow
|
|
top = (((branchLine.first - m_visibleStart) / 4) * m_rowHeight) + (m_rowHeight / 2);
|
|
}
|
|
|
|
if (branchLine.second < m_visibleStart)
|
|
{
|
|
bottom = -1;
|
|
}
|
|
else if (branchLine.second >= visibleEnd)
|
|
{
|
|
bottom = winBottom + 1;
|
|
}
|
|
else
|
|
{
|
|
bottom = (((branchLine.second - m_visibleStart) / 4) * m_rowHeight) + (m_rowHeight / 2);
|
|
}
|
|
|
|
if ((top < 0 && bottom < 0) || (top > winBottom && bottom > winBottom) || (top < 0 && bottom > winBottom) || (top > winBottom && bottom < 0))
|
|
{
|
|
skippedBranches++;
|
|
continue;
|
|
}
|
|
|
|
branchCount++;
|
|
|
|
if (branchLine.first == m_selectedAddressStart || branchLine.second == m_selectedAddressStart)
|
|
{
|
|
painter.setPen(QColor(0xFF257AFA));
|
|
}
|
|
else
|
|
{
|
|
painter.setPen(QColor(0xFFFF3020));
|
|
}
|
|
|
|
if (top < 0) // first is not visible, but second is
|
|
{
|
|
painter.drawLine(x - 2, bottom, x + 2, bottom);
|
|
painter.drawLine(x + 2, bottom, x + 2, 0);
|
|
|
|
if (branchLine.type == LINE_DOWN)
|
|
{
|
|
painter.drawLine(x, bottom - 4, x - 4, bottom);
|
|
painter.drawLine(x - 4, bottom, x + 1, bottom + 5);
|
|
}
|
|
}
|
|
else if (bottom > winBottom) // second is not visible, but first is
|
|
{
|
|
painter.drawLine(x - 2, top, x + 2, top);
|
|
painter.drawLine(x + 2, top, x + 2, winBottom);
|
|
|
|
if (branchLine.type == LINE_UP)
|
|
{
|
|
painter.drawLine(x, top - 4, x - 4, top);
|
|
painter.drawLine(x - 4, top, x + 1, top + 5);
|
|
}
|
|
}
|
|
else
|
|
{ // both are visible
|
|
if (branchLine.type == LINE_UP)
|
|
{
|
|
painter.drawLine(x - 2, bottom, x + 2, bottom);
|
|
painter.drawLine(x + 2, bottom, x + 2, top);
|
|
painter.drawLine(x + 2, top, x - 4, top);
|
|
|
|
painter.drawLine(x, top - 4, x - 4, top);
|
|
painter.drawLine(x - 4, top, x + 1, top + 5);
|
|
}
|
|
else
|
|
{
|
|
painter.drawLine(x - 2, top, x + 2, top);
|
|
painter.drawLine(x + 2, top, x + 2, bottom);
|
|
painter.drawLine(x + 2, bottom, x - 4, bottom);
|
|
|
|
painter.drawLine(x, bottom - 4, x - 4, bottom);
|
|
painter.drawLine(x - 4, bottom, x + 1, bottom + 5);
|
|
}
|
|
}
|
|
}
|
|
// Draw a border
|
|
painter.setPen(this->palette().shadow().color());
|
|
painter.drawRect(0, 0, w, h);
|
|
}
|
|
|
|
void DisassemblyWidget::mousePressEvent(QMouseEvent* event)
|
|
{
|
|
const u32 selectedAddress = (static_cast<int>(event->position().y()) / m_rowHeight * 4) + m_visibleStart;
|
|
if (event->buttons() & Qt::LeftButton)
|
|
{
|
|
if (event->modifiers() & Qt::ShiftModifier)
|
|
{
|
|
if (selectedAddress < m_selectedAddressStart)
|
|
{
|
|
m_selectedAddressStart = selectedAddress;
|
|
}
|
|
else if (selectedAddress > m_visibleStart)
|
|
{
|
|
m_selectedAddressEnd = selectedAddress;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_selectedAddressStart = selectedAddress;
|
|
m_selectedAddressEnd = selectedAddress;
|
|
}
|
|
}
|
|
else if (event->buttons() & Qt::RightButton)
|
|
{
|
|
if (m_selectedAddressStart == m_selectedAddressEnd)
|
|
{
|
|
m_selectedAddressStart = selectedAddress;
|
|
m_selectedAddressEnd = selectedAddress;
|
|
}
|
|
}
|
|
this->repaint();
|
|
}
|
|
|
|
void DisassemblyWidget::mouseDoubleClickEvent(QMouseEvent* event)
|
|
{
|
|
if (!m_cpu->isAlive())
|
|
return;
|
|
|
|
const u32 selectedAddress = (static_cast<int>(event->position().y()) / m_rowHeight * 4) + m_visibleStart;
|
|
if (CBreakPoints::IsAddressBreakPoint(m_cpu->getCpuType(), selectedAddress))
|
|
{
|
|
Host::RunOnCPUThread([&] { CBreakPoints::RemoveBreakPoint(m_cpu->getCpuType(), selectedAddress); });
|
|
}
|
|
else
|
|
{
|
|
Host::RunOnCPUThread([&] { CBreakPoints::AddBreakPoint(m_cpu->getCpuType(), selectedAddress); });
|
|
}
|
|
breakpointsChanged();
|
|
this->repaint();
|
|
}
|
|
|
|
void DisassemblyWidget::wheelEvent(QWheelEvent* event)
|
|
{
|
|
if (event->angleDelta().y() < 0) // todo: max address bounds check?
|
|
{
|
|
m_visibleStart += 4;
|
|
}
|
|
else if (event->angleDelta().y() && m_visibleStart > 0)
|
|
{
|
|
m_visibleStart -= 4;
|
|
}
|
|
this->repaint();
|
|
}
|
|
|
|
void DisassemblyWidget::keyPressEvent(QKeyEvent* event)
|
|
{
|
|
switch (event->key())
|
|
{
|
|
case Qt::Key_Up:
|
|
{
|
|
m_selectedAddressStart -= 4;
|
|
if (!(event->modifiers() & Qt::ShiftModifier))
|
|
m_selectedAddressEnd = m_selectedAddressStart;
|
|
|
|
// Auto scroll
|
|
if (m_visibleStart > m_selectedAddressStart)
|
|
m_visibleStart -= 4;
|
|
}
|
|
break;
|
|
case Qt::Key_PageUp:
|
|
{
|
|
m_selectedAddressStart -= m_visibleRows * 4;
|
|
m_selectedAddressEnd = m_selectedAddressStart;
|
|
m_visibleStart -= m_visibleRows * 4;
|
|
}
|
|
break;
|
|
case Qt::Key_Down:
|
|
{
|
|
m_selectedAddressEnd += 4;
|
|
if (!(event->modifiers() & Qt::ShiftModifier))
|
|
m_selectedAddressStart = m_selectedAddressEnd;
|
|
|
|
// Purposely scroll on the second to last row. It's possible to
|
|
// size the window so part of a row is visible and we don't want to have half a row selected and cut off!
|
|
if (m_visibleStart + ((m_visibleRows - 1) * 4) < m_selectedAddressEnd)
|
|
m_visibleStart += 4;
|
|
|
|
break;
|
|
}
|
|
case Qt::Key_PageDown:
|
|
{
|
|
m_selectedAddressStart += m_visibleRows * 4;
|
|
m_selectedAddressEnd = m_selectedAddressStart;
|
|
m_visibleStart += m_visibleRows * 4;
|
|
break;
|
|
}
|
|
case Qt::Key_G:
|
|
contextGoToAddress();
|
|
break;
|
|
case Qt::Key_C:
|
|
contextCopyInstructionText();
|
|
break;
|
|
case Qt::Key_B:
|
|
contextToggleBreakpoint();
|
|
break;
|
|
case Qt::Key_M:
|
|
contextAssembleInstruction();
|
|
break;
|
|
case Qt::Key_Right:
|
|
contextFollowBranch();
|
|
break;
|
|
case Qt::Key_Left:
|
|
gotoAddress(m_cpu->getPC());
|
|
break;
|
|
}
|
|
|
|
this->repaint();
|
|
}
|
|
|
|
void DisassemblyWidget::customMenuRequested(QPoint pos)
|
|
{
|
|
if (!m_cpu->isAlive())
|
|
return;
|
|
|
|
m_contextMenu->popup(this->mapToGlobal(pos));
|
|
}
|
|
|
|
inline QString DisassemblyWidget::DisassemblyStringFromAddress(u32 address, QFont font, u32 pc)
|
|
{
|
|
DisassemblyLineInfo line;
|
|
|
|
if (!m_cpu->isValidAddress(address))
|
|
return tr("%1 NOT VALID ADDRESS").arg(address, 8, 16, QChar('0')).toUpper();
|
|
// Todo? support non symbol view?
|
|
m_disassemblyManager.getLine(address, true, line);
|
|
|
|
const bool isConditional = line.info.isConditional && m_cpu->getPC() == address;
|
|
const bool isConditionalMet = line.info.conditionMet;
|
|
const bool isCurrentPC = m_cpu->getPC() == address;
|
|
|
|
const bool isBreakpoint = CBreakPoints::IsAddressBreakPoint(m_cpu->getCpuType(), address) && !CBreakPoints::IsTempBreakPoint(m_cpu->getCpuType(), address);
|
|
const std::string addressSymbol = m_cpu->GetSymbolMap().GetLabelString(address);
|
|
|
|
QString lineString("%1 %2 %3 %4 %5 %6");
|
|
|
|
lineString = lineString.arg(isBreakpoint ? "\u25A0" : " "); // Bp block ( ■ )
|
|
if (addressSymbol.empty()) // The address wont have symbol text if it's the start of a function for example
|
|
lineString = lineString.arg(address, 8, 16, QChar('0')).toUpper();
|
|
else
|
|
{
|
|
// We want this text elided
|
|
QFontMetrics metric(font);
|
|
lineString = lineString.arg(metric.elidedText(QString::fromStdString(addressSymbol), Qt::ElideRight, 8 * font.pointSize()));
|
|
}
|
|
|
|
lineString = lineString.leftJustified(4, ' ') // Address / symbol
|
|
.arg(line.name.c_str())
|
|
.arg(line.params.c_str()) // opcode + arguments
|
|
.arg(isConditional ? (isConditionalMet ? "# true" : "# false") : "")
|
|
.arg(isCurrentPC ? "<--" : "");
|
|
|
|
return lineString;
|
|
}
|
|
|
|
QColor DisassemblyWidget::GetAddressFunctionColor(u32 address)
|
|
{
|
|
// This is an attempt to figure out if the current palette is dark or light
|
|
// We calculate the luminescence of the alternateBase colour
|
|
// and swap between our darker and lighter function colours
|
|
|
|
std::array<QColor, 6> colors;
|
|
const QColor base = this->palette().alternateBase().color();
|
|
|
|
const auto Y = (base.redF() * 0.33) + (0.5 * base.greenF()) + (0.16 * base.blueF());
|
|
|
|
if (Y > 0.5)
|
|
{
|
|
colors = {
|
|
QColor::fromRgba(0xFFFA3434),
|
|
QColor::fromRgba(0xFF206b6b),
|
|
QColor::fromRgba(0xFF858534),
|
|
QColor::fromRgba(0xFF378c37),
|
|
QColor::fromRgba(0xFF783278),
|
|
QColor::fromRgba(0xFF21214a),
|
|
};
|
|
}
|
|
else
|
|
{
|
|
colors = {
|
|
QColor::fromRgba(0xFFe05555),
|
|
QColor::fromRgba(0xFF55e0e0),
|
|
QColor::fromRgba(0xFFe8e855),
|
|
QColor::fromRgba(0xFF55e055),
|
|
QColor::fromRgba(0xFFe055e0),
|
|
QColor::fromRgba(0xFFC2C2F5),
|
|
};
|
|
}
|
|
|
|
const auto funNum = m_cpu->GetSymbolMap().GetFunctionNum(address);
|
|
if (funNum == -1)
|
|
return this->palette().text().color();
|
|
|
|
return colors[funNum % 6];
|
|
}
|
|
|
|
QString DisassemblyWidget::FetchSelectionInfo(SelectionInfo selInfo)
|
|
{
|
|
QString infoBlock;
|
|
for (u32 i = m_selectedAddressStart; i <= m_selectedAddressEnd; i += 4)
|
|
{
|
|
if (i != m_selectedAddressStart)
|
|
infoBlock += '\n';
|
|
if (selInfo == SelectionInfo::ADDRESS)
|
|
{
|
|
infoBlock += FilledQStringFromValue(i, 16);
|
|
}
|
|
else if (selInfo == SelectionInfo::INSTRUCTIONTEXT)
|
|
{
|
|
DisassemblyLineInfo line;
|
|
m_disassemblyManager.getLine(i, true, line);
|
|
infoBlock += QString("%1 %2").arg(line.name.c_str()).arg(line.params.c_str());
|
|
}
|
|
else // INSTRUCTIONHEX
|
|
{
|
|
infoBlock += FilledQStringFromValue(m_cpu->read32(i), 16);
|
|
}
|
|
}
|
|
return infoBlock;
|
|
}
|
|
|
|
void DisassemblyWidget::gotoAddress(u32 address)
|
|
{
|
|
const u32 destAddress = address & ~3;
|
|
// Center the address
|
|
m_visibleStart = (destAddress - (m_visibleRows * 4 / 2)) & ~3;
|
|
m_selectedAddressStart = destAddress;
|
|
m_selectedAddressEnd = destAddress;
|
|
|
|
this->repaint();
|
|
this->setFocus();
|
|
}
|