/* 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 .
*/
#include "PrecompiledHeader.h"
#include "CpuWidget.h"
#include "DisassemblyWidget.h"
#include "BreakpointDialog.h"
#include "DebugTools/DebugInterface.h"
#include "DebugTools/Breakpoints.h"
#include "DebugTools/BiosDebugData.h"
#include "DebugTools/MipsStackWalk.h"
#include "common/BitCast.h"
#include "QtUtils.h"
#include
#include
#include
#include
#include
#include "demangler/demangler.h"
using namespace QtUtils;
using namespace MipsStackWalk;
CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu)
: m_cpu(cpu)
{
m_ui.setupUi(this);
connect(g_emu_thread, &EmuThread::onVMPaused, this, &CpuWidget::onVMPaused);
connect(m_ui.registerWidget, &RegisterWidget::gotoInDisasm, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddress);
connect(m_ui.memoryviewWidget, &MemoryViewWidget::gotoInDisasm, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddress);
connect(m_ui.registerWidget, &RegisterWidget::gotoInMemory, m_ui.memoryviewWidget, &MemoryViewWidget::gotoAddress);
connect(m_ui.disassemblyWidget, &DisassemblyWidget::gotoInMemory, m_ui.memoryviewWidget, &MemoryViewWidget::gotoAddress);
connect(m_ui.memoryviewWidget, &MemoryViewWidget::VMUpdate, this, &CpuWidget::reloadCPUWidgets);
connect(m_ui.registerWidget, &RegisterWidget::VMUpdate, this, &CpuWidget::reloadCPUWidgets);
connect(m_ui.disassemblyWidget, &DisassemblyWidget::VMUpdate, this, &CpuWidget::reloadCPUWidgets);
connect(m_ui.tabWidget, &QTabWidget::currentChanged, [this] { fixBPListColumnSize(); });
connect(m_ui.breakpointList, &QTableWidget::customContextMenuRequested, this, &CpuWidget::onBPListContextMenu);
connect(m_ui.breakpointList, &QTableWidget::itemChanged, this, &CpuWidget::onBPListItemChange);
connect(m_ui.threadList, &QTableWidget::customContextMenuRequested, this, &CpuWidget::onThreadListContextMenu);
connect(m_ui.threadList, &QTableWidget::cellDoubleClicked, this, &CpuWidget::onThreadListDoubleClick);
connect(m_ui.threadList, &QTableWidget::customContextMenuRequested, this, &CpuWidget::onThreadListContextMenu);
connect(m_ui.threadList, &QTableWidget::cellDoubleClicked, this, &CpuWidget::onThreadListDoubleClick);
connect(m_ui.stackframeList, &QTableWidget::customContextMenuRequested, this, &CpuWidget::onStackListContextMenu);
connect(m_ui.stackframeList, &QTableWidget::cellDoubleClicked, this, &CpuWidget::onStackListDoubleClick);
connect(m_ui.tabWidgetRegFunc, &QTabWidget::currentChanged, [this](int i) {if(i == 1){updateFunctionList(true);} });
connect(m_ui.listFunctions, &QListWidget::customContextMenuRequested, this, &CpuWidget::onFuncListContextMenu);
connect(m_ui.listFunctions, &QListWidget::itemDoubleClicked, this, &CpuWidget::onFuncListDoubleClick);
connect(m_ui.btnRefreshFunctions, &QPushButton::clicked, [this] { updateFunctionList(); });
connect(m_ui.txtFuncSearch, &QLineEdit::textChanged, [this] { updateFunctionList(); });
connect(m_ui.btnSearch, &QPushButton::clicked, this, &CpuWidget::onSearchButtonClicked);
connect(m_ui.listSearchResults, &QListWidget::itemDoubleClicked, [this](QListWidgetItem* item) { m_ui.memoryviewWidget->gotoAddress(item->data(256).toUInt()); });
connect(m_ui.cmbSearchType, &QComboBox::currentIndexChanged, [this](int i) {
if (i < 4)
m_ui.chkSearchHex->setEnabled(true);
else
m_ui.chkSearchHex->setEnabled(false);
});
m_ui.disassemblyWidget->SetCpu(&cpu);
m_ui.registerWidget->SetCpu(&cpu);
m_ui.memoryviewWidget->SetCpu(&cpu);
if (m_cpu.getCpuType() == BREAKPOINT_EE)
CBreakPoints::SetUpdateHandler(std::bind(&CpuWidget::reloadCPUWidgets, this));
this->repaint();
}
CpuWidget::~CpuWidget() = default;
void CpuWidget::paintEvent(QPaintEvent* event)
{
m_ui.registerWidget->update();
m_ui.disassemblyWidget->update();
m_ui.memoryviewWidget->update();
}
void CpuWidget::resizeEvent(QResizeEvent* event)
{
fixBPListColumnSize();
}
// The cpu shouldn't be alive when these are called
// But make sure it isn't just in case
void CpuWidget::onStepInto()
{
if (!m_cpu.isAlive() || !m_cpu.isCpuPaused())
return;
// Allow the cpu to skip this pc if it is a breakpoint
CBreakPoints::SetSkipFirst(m_cpu.getCpuType(), m_cpu.getPC());
const u32 pc = m_cpu.getPC();
const MIPSAnalyst::MipsOpcodeInfo info = MIPSAnalyst::GetOpcodeInfo(&m_cpu, pc);
u32 bpAddr = pc + 0x4; // Default to the next instruction
if (info.isBranch)
{
if (!info.isConditional)
{
bpAddr = info.branchTarget;
}
else
{
if (info.conditionMet)
{
bpAddr = info.branchTarget;
}
else
{
bpAddr = pc + (2 * 4); // Skip branch delay slot
}
}
}
if (info.isSyscall)
bpAddr = info.branchTarget; // Syscalls are always taken
Host::RunOnCPUThread([&] {
CBreakPoints::AddBreakPoint(m_cpu.getCpuType(), bpAddr, true);
m_cpu.resumeCpu();
});
this->repaint();
}
void CpuWidget::onStepOut()
{
if (!m_cpu.isAlive() || !m_cpu.isCpuPaused())
return;
// Allow the cpu to skip this pc if it is a breakpoint
CBreakPoints::SetSkipFirst(m_cpu.getCpuType(), m_cpu.getPC());
if (m_stacklistObjects.size() < 2)
return;
Host::RunOnCPUThread([&] {
CBreakPoints::AddBreakPoint(m_cpu.getCpuType(), m_stacklistObjects.at(1).pc, true);
m_cpu.resumeCpu();
});
this->repaint();
}
void CpuWidget::onStepOver()
{
if (!m_cpu.isAlive() || !m_cpu.isCpuPaused())
return;
const u32 pc = m_cpu.getPC();
const MIPSAnalyst::MipsOpcodeInfo info = MIPSAnalyst::GetOpcodeInfo(&m_cpu, pc);
u32 bpAddr = pc + 0x4; // Default to the next instruction
if (info.isBranch)
{
if (!info.isConditional)
{
if (info.isLinkedBranch) // jal, jalr
{
// it's a function call with a delay slot - skip that too
bpAddr += 4;
}
else // j, ...
{
// in case of absolute branches, set the breakpoint at the branch target
bpAddr = info.branchTarget;
}
}
else // beq, ...
{
if (info.conditionMet)
{
bpAddr = info.branchTarget;
}
else
{
bpAddr = pc + (2 * 4); // Skip branch delay slot
}
}
}
Host::RunOnCPUThread([&] {
CBreakPoints::AddBreakPoint(m_cpu.getCpuType(), bpAddr, true);
m_cpu.resumeCpu();
});
this->repaint();
}
void CpuWidget::onVMPaused()
{
// Stops us from telling the disassembly dialog to jump somwhere because breakpoint code paused the core.
if (CBreakPoints::GetCorePaused())
{
CBreakPoints::SetCorePaused(false);
}
else
{
m_ui.disassemblyWidget->gotoAddress(m_cpu.getPC());
}
reloadCPUWidgets();
this->repaint();
}
void CpuWidget::updateBreakpoints()
{
m_ui.breakpointList->setRowCount(0);
m_bplistObjects.clear();
int iter = 0;
for (const auto& breakpoint : CBreakPoints::GetBreakpoints())
{
if (breakpoint.cpu != m_cpu.getCpuType())
continue;
if (breakpoint.temporary)
continue;
m_ui.breakpointList->insertRow(iter);
BreakpointObject obj;
obj.bp = std::make_shared(breakpoint);
m_bplistObjects.push_back(obj);
// Type (R/O)
QTableWidgetItem* typeItem = new QTableWidgetItem();
typeItem->setText(tr("Execute"));
typeItem->setFlags(Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable);
m_ui.breakpointList->setItem(iter, 0, typeItem);
// Offset (R/O), possibly allow changing offset???
QTableWidgetItem* offsetItem = new QTableWidgetItem();
offsetItem->setText(FilledQStringFromValue(breakpoint.addr, 16));
offsetItem->setFlags(Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable);
m_ui.breakpointList->setItem(iter, 1, offsetItem);
// Size & Label (R/O)
QTableWidgetItem* sizeLabelItem = new QTableWidgetItem();
sizeLabelItem->setText(m_cpu.GetSymbolMap().GetLabelString(breakpoint.addr).c_str());
sizeLabelItem->setFlags(Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable);
m_ui.breakpointList->setItem(iter, 2, sizeLabelItem);
// Opcode (R/O)
QTableWidgetItem* opcodeItem = new QTableWidgetItem();
opcodeItem->setText(m_ui.disassemblyWidget->GetLineDisasm(breakpoint.addr));
opcodeItem->setFlags(Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable);
m_ui.breakpointList->setItem(iter, 3, opcodeItem);
// Condition (R/W)
QTableWidgetItem* conditionItem = new QTableWidgetItem();
conditionItem->setText(breakpoint.hasCond ? QString::fromLocal8Bit(breakpoint.cond.expressionString) : "");
conditionItem->setFlags(Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEditable);
m_ui.breakpointList->setItem(iter, 4, conditionItem);
// Hits (R/O) (Disabled for execute bp)
QTableWidgetItem* hitsItem = new QTableWidgetItem();
hitsItem->setText("N/A");
hitsItem->setFlags(Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable);
m_ui.breakpointList->setItem(iter, 5, hitsItem);
// Enabled (R/W)
QTableWidgetItem* enabledItem = new QTableWidgetItem();
enabledItem->setCheckState(breakpoint.enabled ? Qt::Checked : Qt::Unchecked);
enabledItem->setFlags(Qt::ItemFlag::ItemIsUserCheckable | Qt::ItemFlag::ItemIsEnabled);
m_ui.breakpointList->setItem(iter, 6, enabledItem);
iter++;
}
for (const auto& memcheck : CBreakPoints::GetMemChecks())
{
if (memcheck.cpu != m_cpu.getCpuType())
continue;
m_ui.breakpointList->insertRow(iter);
BreakpointObject obj;
obj.mc = std::make_shared(memcheck);
m_bplistObjects.push_back(obj);
// Type (R/O)
QTableWidgetItem* typeItem = new QTableWidgetItem();
QString type("");
type += memcheck.cond & MEMCHECK_READ ? tr("Read") : "";
type += ((memcheck.cond & MEMCHECK_BOTH) == MEMCHECK_BOTH) ? ", " : " ";
type += memcheck.cond & MEMCHECK_WRITE ? memcheck.cond & MEMCHECK_WRITE_ONCHANGE ? tr("Write(C)") : tr("Write") : "";
typeItem->setText(type);
typeItem->setFlags(Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable);
m_ui.breakpointList->setItem(iter, 0, typeItem);
// Offset (R/O), possibly allow changing offset?
QTableWidgetItem* offsetItem = new QTableWidgetItem();
offsetItem->setText(FilledQStringFromValue(memcheck.start, 16));
offsetItem->setFlags(Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable);
m_ui.breakpointList->setItem(iter, 1, offsetItem);
// Size & Label (R/O)
QTableWidgetItem* sizeLabelItem = new QTableWidgetItem();
sizeLabelItem->setText(QString::number(memcheck.end - memcheck.start, 16));
sizeLabelItem->setFlags(Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEditable);
m_ui.breakpointList->setItem(iter, 2, sizeLabelItem);
// Opcode (R/O)
QTableWidgetItem* opcodeItem = new QTableWidgetItem();
opcodeItem->setText(m_ui.disassemblyWidget->GetLineDisasm(memcheck.start));
opcodeItem->setFlags(Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable);
m_ui.breakpointList->setItem(iter, 3, opcodeItem);
// Condition (R/W) (Disabled for memchecks)
QTableWidgetItem* conditionItem = new QTableWidgetItem();
conditionItem->setText("N/A");
conditionItem->setFlags(Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable);
m_ui.breakpointList->setItem(iter, 4, conditionItem);
// Hits (R/O)
QTableWidgetItem* hitsItem = new QTableWidgetItem();
hitsItem->setText(QString::number(memcheck.numHits));
hitsItem->setFlags(Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable);
m_ui.breakpointList->setItem(iter, 5, hitsItem);
// Enabled (R/W)
QTableWidgetItem* enabledItem = new QTableWidgetItem();
enabledItem->setCheckState((memcheck.result & MEMCHECK_BREAK) ? Qt::Checked : Qt::Unchecked);
enabledItem->setFlags(Qt::ItemFlag::ItemIsUserCheckable | Qt::ItemFlag::ItemIsEnabled);
m_ui.breakpointList->setItem(iter, 6, enabledItem);
iter++;
}
}
void CpuWidget::fixBPListColumnSize()
{
m_ui.breakpointList->horizontalHeader()->resizeSection(0, 90);
m_ui.breakpointList->horizontalHeader()->resizeSection(1, 65);
m_ui.breakpointList->horizontalHeader()->resizeSection(5, 40);
m_ui.breakpointList->horizontalHeader()->resizeSection(6, 60);
constexpr int currentWidthTotal = 90 + 65 + 40 + 60;
const int sectionWidth = (m_ui.breakpointList->width() - currentWidthTotal) / 3.0f;
m_ui.breakpointList->horizontalHeader()->resizeSection(2, sectionWidth);
m_ui.breakpointList->horizontalHeader()->resizeSection(3, sectionWidth);
m_ui.breakpointList->horizontalHeader()->resizeSection(4, sectionWidth);
}
void CpuWidget::onBPListContextMenu(QPoint pos)
{
if (m_bplistContextMenu)
delete m_bplistContextMenu;
m_bplistContextMenu = new QMenu(m_ui.breakpointList);
QAction* newAction = new QAction(tr("New"), m_ui.breakpointList);
connect(newAction, &QAction::triggered, this, &CpuWidget::contextBPListNew);
m_bplistContextMenu->addAction(newAction);
const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel();
if (selModel->hasSelection())
{
QAction* editAction = new QAction(tr("Edit"), m_ui.breakpointList);
connect(editAction, &QAction::triggered, this, &CpuWidget::contextBPListEdit);
m_bplistContextMenu->addAction(editAction);
// Only copy when one column is selected
// Shouldn't be trivial to support cross column copy
if (selModel->selectedIndexes().count() == 1)
{
QAction* copyAction = new QAction(tr("Copy"), m_ui.breakpointList);
connect(copyAction, &QAction::triggered, this, &CpuWidget::contextBPListCopy);
m_bplistContextMenu->addAction(copyAction);
}
QAction* deleteAction = new QAction(tr("Delete"), m_ui.breakpointList);
connect(deleteAction, &QAction::triggered, this, &CpuWidget::contextBPListDelete);
m_bplistContextMenu->addAction(deleteAction);
}
m_bplistContextMenu->popup(m_ui.breakpointList->mapToGlobal(pos));
}
void CpuWidget::onBPListItemChange(QTableWidgetItem* item)
{
if (item->column() == 2 && m_bplistObjects.at(item->row()).mc) // Size / Label column. Size is editable for memchecks
{
const auto& mc = m_bplistObjects.at(item->row()).mc;
bool ok;
u32 val = item->text().toUInt(&ok, 16);
if (!ok)
{
QMessageBox::warning(this, tr("Error"), tr("Invalid size \"%1\"").arg(item->text()));
item->setText(QString::number((mc->end - mc->start), 16));
return;
}
if (val == (mc->end - mc->start))
{
return;
}
Host::RunOnCPUThread([this, val, mc] {
CBreakPoints::RemoveMemCheck(m_cpu.getCpuType(), mc->start, mc->end);
CBreakPoints::AddMemCheck(m_cpu.getCpuType(), mc->start, mc->start + val, mc->cond, mc->result);
});
updateBreakpoints();
}
else if (item->column() == 4 && m_bplistObjects.at(item->row()).bp) // Condition column. Only editable for breakpoints
{
const auto& bp = m_bplistObjects.at(item->row()).bp;
if (item->text().isEmpty() && bp->hasCond)
{
Host::RunOnCPUThread([this, bp] {
CBreakPoints::ChangeBreakPointRemoveCond(m_cpu.getCpuType(), bp->addr);
});
updateBreakpoints();
}
else if (item->text() != QString::fromLocal8Bit(&bp->cond.expressionString[0]))
{
PostfixExpression expression;
if (!m_cpu.initExpression(item->text().toLocal8Bit().constData(), expression))
{
QMessageBox::warning(this, tr("Error"), tr("Invalid condition \"%1\"").arg(item->text()));
item->setText(QString::fromLocal8Bit(&bp->cond.expressionString[0]));
return;
}
BreakPointCond cond;
cond.debug = &m_cpu;
cond.expression = expression;
strcpy(&cond.expressionString[0], item->text().toLocal8Bit().constData());
Host::RunOnCPUThread([this, bp, cond] {
CBreakPoints::ChangeBreakPointAddCond(m_cpu.getCpuType(), bp->addr, cond);
});
updateBreakpoints();
}
}
}
void CpuWidget::contextBPListCopy()
{
const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel();
if (!selModel->hasSelection())
return;
QGuiApplication::clipboard()->setText(m_ui.breakpointList->selectedItems().first()->text());
}
void CpuWidget::contextBPListDelete()
{
const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel();
if (!selModel->hasSelection())
return;
int last_row = -1;
for (auto& index : selModel->selectedIndexes())
{
if (index.row() == last_row) // If the next index is in the same row, don't delete that breakpoint twice!
continue;
auto& bpObject = m_bplistObjects.at(index.row());
Host::RunOnCPUThread([&] {
if (bpObject.bp)
{
CBreakPoints::RemoveBreakPoint(m_cpu.getCpuType(), bpObject.bp->addr);
}
else
{
CBreakPoints::RemoveMemCheck(m_cpu.getCpuType(), bpObject.mc->start, bpObject.mc->end);
}
});
last_row = index.row();
}
updateBreakpoints();
}
void CpuWidget::contextBPListNew()
{
BreakpointDialog* bpDialog = new BreakpointDialog(this, &m_cpu);
connect(bpDialog, &BreakpointDialog::accepted, this, &CpuWidget::updateBreakpoints);
bpDialog->show();
}
void CpuWidget::contextBPListEdit()
{
const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel();
if (!selModel->hasSelection())
return;
auto& bpObject = m_bplistObjects.at(selModel->selectedIndexes().first().row());
BreakpointDialog* bpDialog;
if (bpObject.bp)
{
bpDialog = new BreakpointDialog(this, &m_cpu, bpObject.bp.get());
}
else
{
bpDialog = new BreakpointDialog(this, &m_cpu, bpObject.mc.get());
}
connect(bpDialog, &BreakpointDialog::accepted, this, &CpuWidget::updateBreakpoints);
bpDialog->show();
}
void CpuWidget::updateFunctionList(bool whenEmpty)
{
if (!m_cpu.isAlive())
return;
if (whenEmpty && m_ui.listFunctions->count())
return;
m_ui.listFunctions->clear();
const auto demangler = demangler::CDemangler::createGcc();
const QString filter = m_ui.txtFuncSearch->text().toLower();
for (const auto& symbol : m_cpu.GetSymbolMap().GetAllSymbols(SymbolType::ST_FUNCTION))
{
QString symbolName = symbol.name.c_str();
if (m_demangleFunctions)
{
symbolName = QString(demangler->demangleToString(symbol.name).c_str());
// If the name isn't mangled, or it doesn't understand, it'll return an empty string
// Fall back to the original name if this is the case
if (symbolName.isEmpty())
symbolName = symbol.name.c_str();
}
if (filter.size() && !symbolName.toLower().contains(filter))
continue;
QListWidgetItem* item = new QListWidgetItem();
item->setText(QString("%0 %1").arg(FilledQStringFromValue(symbol.address, 16)).arg(symbolName));
item->setData(256, symbol.address);
m_ui.listFunctions->addItem(item);
}
}
void CpuWidget::updateThreads()
{
m_ui.threadList->setRowCount(0);
if (m_cpu.getCpuType() == BREAKPOINT_EE)
m_threadlistObjects = getEEThreads();
for (size_t i = 0; i < m_threadlistObjects.size(); i++)
{
m_ui.threadList->insertRow(i);
const auto& thread = m_threadlistObjects[i];
if (thread.data.status == THS_RUN)
m_activeThread = thread;
QTableWidgetItem* idItem = new QTableWidgetItem();
idItem->setText(QString::number(thread.tid));
idItem->setFlags(Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled);
m_ui.threadList->setItem(i, 0, idItem);
QTableWidgetItem* pcItem = new QTableWidgetItem();
if (thread.data.status == THS_RUN)
pcItem->setText(FilledQStringFromValue(m_cpu.getPC(), 16));
else
pcItem->setText(FilledQStringFromValue(thread.data.entry, 16));
pcItem->setFlags(Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled);
m_ui.threadList->setItem(i, 1, pcItem);
QTableWidgetItem* entryItem = new QTableWidgetItem();
entryItem->setText(FilledQStringFromValue(thread.data.entry_init, 16));
entryItem->setFlags(Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled);
m_ui.threadList->setItem(i, 2, entryItem);
QTableWidgetItem* priorityItem = new QTableWidgetItem();
priorityItem->setText(QString::number(thread.data.currentPriority));
priorityItem->setFlags(Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled);
m_ui.threadList->setItem(i, 3, priorityItem);
QString statusString;
switch (thread.data.status)
{
case THS_BAD:
statusString = tr("Bad");
break;
case THS_RUN:
statusString = tr("Running");
break;
case THS_READY:
statusString = tr("Ready");
break;
case THS_WAIT:
statusString = tr("Waiting");
break;
case THS_SUSPEND:
statusString = tr("Suspended");
break;
case THS_WAIT_SUSPEND:
statusString = tr("Waiting/Suspended");
break;
case THS_DORMANT:
statusString = tr("Dormant");
break;
}
QTableWidgetItem* statusItem = new QTableWidgetItem();
statusItem->setText(statusString);
statusItem->setFlags(Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled);
m_ui.threadList->setItem(i, 4, statusItem);
QString waitTypeString;
switch (thread.data.waitType)
{
case WAIT_NONE:
waitTypeString = tr("None");
break;
case WAIT_WAKEUP_REQ:
waitTypeString = tr("Wakeup request");
break;
case WAIT_SEMA:
waitTypeString = tr("Semaphore");
break;
}
QTableWidgetItem* waitItem = new QTableWidgetItem();
waitItem->setText(waitTypeString);
waitItem->setFlags(Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled);
m_ui.threadList->setItem(i, 5, waitItem);
}
}
void CpuWidget::onThreadListContextMenu(QPoint pos)
{
if (!m_threadlistContextMenu)
{
m_threadlistContextMenu = new QMenu(m_ui.threadList);
QAction* copyAction = new QAction(tr("Copy"), m_ui.threadList);
connect(copyAction, &QAction::triggered, [this] {
const auto& items = m_ui.threadList->selectedItems();
if (!items.size())
return;
QApplication::clipboard()->setText(items.first()->text());
});
m_threadlistContextMenu->addAction(copyAction);
}
m_threadlistContextMenu->exec(m_ui.threadList->mapToGlobal(pos));
}
void CpuWidget::onThreadListDoubleClick(int row, int column)
{
const auto& entry = m_threadlistObjects.at(row);
if (column == 1) // PC
{
if (entry.data.status == THS_RUN)
m_ui.disassemblyWidget->gotoAddress(m_cpu.getPC());
else
m_ui.disassemblyWidget->gotoAddress(entry.data.entry);
}
else if (column == 2) // Entry Point
{
m_ui.disassemblyWidget->gotoAddress(entry.data.entry_init);
}
}
void CpuWidget::onFuncListContextMenu(QPoint pos)
{
if (!m_funclistContextMenu)
m_funclistContextMenu = new QMenu(m_ui.listFunctions);
else
m_funclistContextMenu->clear();
QAction* demangleAction = new QAction(tr("Demangle Symbols"), m_ui.listFunctions);
demangleAction->setCheckable(true);
demangleAction->setChecked(m_demangleFunctions);
connect(demangleAction, &QAction::triggered, [this] {
m_demangleFunctions = !m_demangleFunctions;
updateFunctionList();
});
m_funclistContextMenu->addAction(demangleAction);
QAction* copyName = new QAction(tr("Copy Function Name"), m_ui.listFunctions);
connect(copyName, &QAction::triggered, [this] {
// We only store the address in the widget item
// Resolve the function name by fetching the symbolmap and filtering the address
const QListWidgetItem* selectedItem = m_ui.listFunctions->selectedItems().first();
const QString functionName = QString(m_cpu.GetSymbolMap().GetLabelString(selectedItem->data(256).toUInt()).c_str());
QApplication::clipboard()->setText(functionName);
});
m_funclistContextMenu->addAction(copyName);
QAction* copyAddress = new QAction(tr("Copy Function Address"), m_ui.listFunctions);
connect(copyAddress, &QAction::triggered, [this] {
const QString addressString = FilledQStringFromValue(m_ui.listFunctions->selectedItems().first()->data(256).toUInt(), 16);
QApplication::clipboard()->setText(addressString);
});
m_funclistContextMenu->addAction(copyAddress);
m_funclistContextMenu->addSeparator();
QAction* gotoDisasm = new QAction(tr("Go to in Disassembly"), m_ui.listFunctions);
connect(gotoDisasm, &QAction::triggered, [this] {
m_ui.disassemblyWidget->gotoAddress(m_ui.listFunctions->selectedItems().first()->data(256).toUInt());
});
m_funclistContextMenu->addAction(gotoDisasm);
QAction* gotoMemory = new QAction(tr("Go to in Memory View"), m_ui.listFunctions);
connect(gotoMemory, &QAction::triggered, [this] {
m_ui.memoryviewWidget->gotoAddress(m_ui.listFunctions->selectedItems().first()->data(256).toUInt());
});
m_funclistContextMenu->addAction(gotoMemory);
m_funclistContextMenu->popup(m_ui.listFunctions->mapToGlobal(pos));
}
void CpuWidget::onFuncListDoubleClick(QListWidgetItem* item)
{
m_ui.disassemblyWidget->gotoAddress(item->data(256).toUInt());
}
void CpuWidget::updateStackFrames()
{
m_ui.stackframeList->setRowCount(0);
m_stacklistObjects = MipsStackWalk::Walk(&m_cpu, m_cpu.getPC(), m_cpu.getRegister(0, 31), m_cpu.getRegister(0, 29),
m_activeThread.data.entry_init, m_activeThread.data.stack);
for (size_t i = 0; i < m_stacklistObjects.size(); i++)
{
m_ui.stackframeList->insertRow(i);
const auto& stackFrame = m_stacklistObjects.at(i);
QTableWidgetItem* entryItem = new QTableWidgetItem();
entryItem->setText(FilledQStringFromValue(stackFrame.entry, 16));
entryItem->setFlags(Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled);
m_ui.stackframeList->setItem(i, 0, entryItem);
QTableWidgetItem* entryName = new QTableWidgetItem();
entryName->setText(m_cpu.GetSymbolMap().GetLabelString(stackFrame.entry).c_str());
entryName->setFlags(Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled);
m_ui.stackframeList->setItem(i, 1, entryName);
QTableWidgetItem* entryPC = new QTableWidgetItem();
entryPC->setText(FilledQStringFromValue(stackFrame.pc, 16));
entryPC->setFlags(Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled);
m_ui.stackframeList->setItem(i, 2, entryPC);
QTableWidgetItem* entryOpcode = new QTableWidgetItem();
entryOpcode->setText(m_ui.disassemblyWidget->GetLineDisasm(stackFrame.pc));
entryOpcode->setFlags(Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled);
m_ui.stackframeList->setItem(i, 3, entryOpcode);
QTableWidgetItem* entrySP = new QTableWidgetItem();
entrySP->setText(FilledQStringFromValue(stackFrame.sp, 16));
entrySP->setFlags(Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled);
m_ui.stackframeList->setItem(i, 4, entrySP);
QTableWidgetItem* entryStackSize = new QTableWidgetItem();
entryStackSize->setText(QString::number(stackFrame.stackSize));
entryStackSize->setFlags(Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled);
m_ui.stackframeList->setItem(i, 5, entryStackSize);
}
}
void CpuWidget::onStackListContextMenu(QPoint pos)
{
if (!m_stacklistContextMenu)
{
m_stacklistContextMenu = new QMenu(m_ui.stackframeList);
QAction* copyAction = new QAction(tr("Copy"), m_ui.stackframeList);
connect(copyAction, &QAction::triggered, [this] {
const auto& items = m_ui.stackframeList->selectedItems();
if (!items.size())
return;
QApplication::clipboard()->setText(items.first()->text());
});
m_stacklistContextMenu->addAction(copyAction);
}
m_stacklistContextMenu->exec(m_ui.stackframeList->mapToGlobal(pos));
}
void CpuWidget::onStackListDoubleClick(int row, int column)
{
const auto& entry = m_stacklistObjects.at(row);
m_ui.disassemblyWidget->gotoAddress(entry.pc);
}
template
static std::vector searchWorker(DebugInterface* cpu, u32 start, u32 end, T value)
{
std::vector hitAddresses;
for (u32 addr = start; addr < end; addr += sizeof(T))
{
T val = 0;
switch (sizeof(T))
{
case sizeof(u8):
val = cpu->read8(addr);
break;
case sizeof(u16):
val = cpu->read16(addr);
break;
case sizeof(u32):
{
if (std::is_same_v)
{
const float fTop = value + 0.00001f;
const float fBottom = value - 0.00001f;
const float memValue = bit_cast(cpu->read32(addr));
if (fBottom < memValue && memValue < fTop)
{
hitAddresses.emplace_back(addr);
}
continue;
}
val = cpu->read32(addr);
break;
}
case sizeof(u64):
{
if (std::is_same_v)
{
const double dTop = value + 0.00001f;
const double dBottom = value - 0.00001f;
const double memValue = bit_cast(cpu->read64(addr));
if (dBottom < memValue && memValue < dTop)
{
hitAddresses.emplace_back(addr);
}
continue;
}
val = cpu->read64(addr);
break;
}
default:
Console.Error("Debugger: Unknown type when doing memory search!");
return hitAddresses;
break;
}
if (val == value)
{
hitAddresses.push_back(addr);
}
}
return hitAddresses;
}
static std::vector searchWorkerString(DebugInterface* cpu, u32 start, u32 end, std::string value)
{
std::vector hitAddresses;
for (u32 addr = start; addr < end; addr += 1)
{
bool hit = true;
for (size_t i = 0; i < value.length(); i++)
{
if (static_cast(cpu->read8(addr + i)) != value[i])
{
hit = false;
break;
}
}
if (hit)
{
hitAddresses.emplace_back(addr);
addr += value.length() - 1;
}
}
return hitAddresses;
}
std::vector startWorker(DebugInterface* cpu, int type, u32 start, u32 end, QString value, int base)
{
const bool isSigned = value.startsWith("-");
switch (type)
{
case 0:
return isSigned ? searchWorker(cpu, start, end, value.toShort(nullptr, base)) : searchWorker(cpu, start, end, value.toUShort(nullptr, base));
case 1:
return isSigned ? searchWorker(cpu, start, end, value.toShort(nullptr, base)) : searchWorker(cpu, start, end, value.toUShort(nullptr, base));
case 2:
return isSigned ? searchWorker(cpu, start, end, value.toInt(nullptr, base)) : searchWorker(cpu, start, end, value.toUInt(nullptr, base));
case 3:
return isSigned ? searchWorker(cpu, start, end, value.toLong(nullptr, base)) : searchWorker(cpu, start, end, value.toULongLong(nullptr, base));
case 4:
return searchWorker(cpu, start, end, value.toFloat());
case 5:
return searchWorker(cpu, start, end, value.toDouble());
case 6:
return searchWorkerString(cpu, start, end, value.toStdString());
default:
Console.Error("Debugger: Unknown type when doing memory search!");
break;
};
return {};
}
void CpuWidget::onSearchButtonClicked()
{
const int searchType = m_ui.cmbSearchType->currentIndex();
const bool searchHex = m_ui.chkSearchHex->isChecked();
bool ok;
const u32 searchStart = m_ui.txtSearchStart->text().toUInt(&ok, 16);
if (!ok)
{
QMessageBox::critical(this, tr("Debugger"), tr("Invalid start address"));
return;
}
const u32 searchEnd = m_ui.txtSearchEnd->text().toUInt(&ok, 16);
if (!ok)
{
QMessageBox::critical(this, tr("Debugger"), tr("Invalid end address"));
return;
}
if (searchStart >= searchEnd)
{
QMessageBox::critical(this, tr("Debugger"), tr("Start address can't be equal to or greater than the end address"));
return;
}
const QString searchValue = m_ui.txtSearchValue->text();
if (searchType < 4)
{
searchValue.toLong(&ok, searchHex ? 16 : 10);
}
else if (searchType != 6)
{
searchValue.toDouble(&ok);
}
if (!ok)
{
QMessageBox::critical(this, tr("Debugger"), tr("Invalid search value"));
return;
}
QFutureWatcher>* workerWatcher = new QFutureWatcher>;
connect(workerWatcher, &QFutureWatcher>::finished, [this, workerWatcher] {
m_ui.btnSearch->setDisabled(false);
m_ui.listSearchResults->clear();
const auto& results = workerWatcher->future().result();
for (const auto& address : results)
{
QListWidgetItem* item = new QListWidgetItem(QtUtils::FilledQStringFromValue(address, 16));
item->setData(256, address);
m_ui.listSearchResults->addItem(item);
}
});
m_ui.btnSearch->setDisabled(true);
QFuture> workerFuture =
QtConcurrent::run(startWorker, &m_cpu, searchType, searchStart, searchEnd, searchValue, searchHex ? 16 : 10);
workerWatcher->setFuture(workerFuture);
}