dolphin/Source/Core/DolphinQt2/Debugger/CodeViewWidget.cpp
Lioncash 880f7871d9 CodeWidget/CodeViewWidget: Make symbol pointers const where applicable
These aren't used to modify the data they point to, so make that
explicit. Also while we're at it, add const to any nearby variables that
can be made so.
2018-05-26 23:43:28 -04:00

564 lines
14 KiB
C++

// Copyright 2018 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DolphinQt2/Debugger/CodeViewWidget.h"
#include <algorithm>
#include <cmath>
#include <QApplication>
#include <QClipboard>
#include <QHeaderView>
#include <QInputDialog>
#include <QKeyEvent>
#include <QMenu>
#include <QMouseEvent>
#include <QResizeEvent>
#include <QScrollBar>
#include <QTableWidgetItem>
#include <QWheelEvent>
#include "Common/StringUtil.h"
#include "Core/Core.h"
#include "Core/Debugger/PPCDebugInterface.h"
#include "Core/PowerPC/MMU.h"
#include "Core/PowerPC/PPCAnalyst.h"
#include "Core/PowerPC/PPCSymbolDB.h"
#include "Core/PowerPC/PowerPC.h"
#include "DolphinQt2/QtUtils/ActionHelper.h"
#include "DolphinQt2/Resources.h"
#include "DolphinQt2/Settings.h"
constexpr size_t VALID_BRANCH_LENGTH = 10;
CodeViewWidget::CodeViewWidget()
{
setColumnCount(5);
setShowGrid(false);
setContextMenuPolicy(Qt::CustomContextMenu);
setSelectionMode(QAbstractItemView::SingleSelection);
setSelectionBehavior(QAbstractItemView::SelectRows);
verticalScrollBar()->setHidden(true);
for (int i = 0; i < columnCount(); i++)
{
horizontalHeader()->setSectionResizeMode(i, QHeaderView::Fixed);
}
verticalHeader()->hide();
horizontalHeader()->hide();
horizontalHeader()->setStretchLastSection(true);
setFont(Settings::Instance().GetDebugFont());
Update();
connect(this, &CodeViewWidget::customContextMenuRequested, this, &CodeViewWidget::OnContextMenu);
connect(this, &CodeViewWidget::itemSelectionChanged, this, &CodeViewWidget::OnSelectionChanged);
connect(&Settings::Instance(), &Settings::DebugFontChanged, this, &QWidget::setFont);
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this] {
m_address = PC;
Update();
});
connect(&Settings::Instance(), &Settings::ThemeChanged, this, &CodeViewWidget::Update);
}
static u32 GetBranchFromAddress(u32 addr)
{
std::string disasm = PowerPC::debug_interface.Disassemble(addr);
size_t pos = disasm.find("->0x");
if (pos == std::string::npos)
return 0;
std::string hex = disasm.substr(pos + 2);
return std::stoul(hex, nullptr, 16);
}
void CodeViewWidget::Update()
{
if (m_updating)
return;
m_updating = true;
clearSelection();
if (rowCount() == 0)
setRowCount(1);
// Calculate (roughly) how many rows will fit in our table
int rows = std::round((height() / static_cast<float>(rowHeight(0))) - 0.25);
setRowCount(rows);
for (int i = 0; i < rows; i++)
setRowHeight(i, 24);
u32 pc = PowerPC::ppcState.pc;
if (Core::GetState() != Core::State::Paused && PowerPC::debug_interface.IsBreakpoint(pc))
Core::SetState(Core::State::Paused);
const bool dark_theme = qApp->palette().color(QPalette::Base).valueF() < 0.5;
for (int i = 0; i < rowCount(); i++)
{
u32 addr = m_address - ((rowCount() / 2) * 4) + i * 4;
u32 color = PowerPC::debug_interface.GetColor(addr);
auto* bp_item = new QTableWidgetItem;
auto* addr_item = new QTableWidgetItem(QStringLiteral("%1").arg(addr, 8, 16, QLatin1Char('0')));
std::string disas = PowerPC::debug_interface.Disassemble(addr);
auto split = disas.find('\t');
std::string ins = (split == std::string::npos ? disas : disas.substr(0, split));
std::string param = (split == std::string::npos ? "" : disas.substr(split + 1));
std::string desc = PowerPC::debug_interface.GetDescription(addr);
auto* ins_item = new QTableWidgetItem(QString::fromStdString(ins));
auto* param_item = new QTableWidgetItem(QString::fromStdString(param));
auto* description_item = new QTableWidgetItem(QString::fromStdString(desc));
for (auto* item : {bp_item, addr_item, ins_item, param_item, description_item})
{
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
item->setData(Qt::UserRole, addr);
if (addr == pc && item != bp_item)
{
item->setBackground(QColor(Qt::green));
item->setForeground(QColor(Qt::black));
}
else if (color != 0xFFFFFF)
{
item->setBackground(dark_theme ? QColor(color).darker(240) : QColor(color));
}
}
// look for hex strings to decode branches
std::string hex_str;
size_t pos = param.find("0x");
if (pos != std::string::npos)
{
hex_str = param.substr(pos);
}
if (hex_str.length() == VALID_BRANCH_LENGTH && desc != "---")
{
description_item->setText(tr("--> %1").arg(QString::fromStdString(
PowerPC::debug_interface.GetDescription(GetBranchFromAddress(addr)))));
param_item->setForeground(Qt::magenta);
}
if (ins == "blr")
ins_item->setForeground(dark_theme ? QColor(0xa0FFa0) : Qt::darkGreen);
if (PowerPC::debug_interface.IsBreakpoint(addr))
{
bp_item->setData(Qt::DecorationRole,
Resources::GetScaledThemeIcon("debugger_breakpoint").pixmap(QSize(24, 24)));
}
setItem(i, 0, bp_item);
setItem(i, 1, addr_item);
setItem(i, 2, ins_item);
setItem(i, 3, param_item);
setItem(i, 4, description_item);
if (addr == GetAddress())
{
selectRow(addr_item->row());
}
}
resizeColumnsToContents();
setColumnWidth(0, 24 + 5);
g_symbolDB.FillInCallers();
repaint();
m_updating = false;
}
u32 CodeViewWidget::GetAddress() const
{
return m_address;
}
void CodeViewWidget::SetAddress(u32 address, SetAddressUpdate update)
{
if (m_address == address)
return;
m_address = address;
if (update == SetAddressUpdate::WithUpdate)
Update();
}
void CodeViewWidget::ReplaceAddress(u32 address, ReplaceWith replace)
{
PowerPC::debug_interface.UnsetPatch(address);
PowerPC::debug_interface.SetPatch(address, replace == ReplaceWith::BLR ? 0x4e800020 : 0x60000000);
Update();
}
void CodeViewWidget::OnContextMenu()
{
QMenu* menu = new QMenu(this);
bool running = Core::GetState() != Core::State::Uninitialized;
const u32 addr = GetContextAddress();
bool has_symbol = g_symbolDB.GetSymbolFromAddr(addr);
auto* follow_branch_action =
AddAction(menu, tr("Follow &branch"), this, &CodeViewWidget::OnFollowBranch);
menu->addSeparator();
AddAction(menu, tr("&Copy address"), this, &CodeViewWidget::OnCopyAddress);
auto* copy_address_action =
AddAction(menu, tr("Copy &function"), this, &CodeViewWidget::OnCopyFunction);
auto* copy_line_action =
AddAction(menu, tr("Copy code &line"), this, &CodeViewWidget::OnCopyCode);
auto* copy_hex_action = AddAction(menu, tr("Copy &hex"), this, &CodeViewWidget::OnCopyHex);
menu->addSeparator();
auto* symbol_rename_action =
AddAction(menu, tr("&Rename symbol"), this, &CodeViewWidget::OnRenameSymbol);
auto* symbol_size_action =
AddAction(menu, tr("Set symbol &size"), this, &CodeViewWidget::OnSetSymbolSize);
auto* symbol_end_action =
AddAction(menu, tr("Set symbol &end address"), this, &CodeViewWidget::OnSetSymbolEndAddress);
menu->addSeparator();
AddAction(menu, tr("Run &To Here"), this, &CodeViewWidget::OnRunToHere);
auto* function_action =
AddAction(menu, tr("&Add function"), this, &CodeViewWidget::OnAddFunction);
auto* ppc_action = AddAction(menu, tr("PPC vs Host"), this, &CodeViewWidget::OnPPCComparison);
auto* insert_blr_action = AddAction(menu, tr("&Insert blr"), this, &CodeViewWidget::OnInsertBLR);
auto* insert_nop_action = AddAction(menu, tr("Insert &nop"), this, &CodeViewWidget::OnInsertNOP);
auto* replace_action =
AddAction(menu, tr("Re&place instruction"), this, &CodeViewWidget::OnReplaceInstruction);
auto* restore_action =
AddAction(menu, tr("Restore instruction"), this, &CodeViewWidget::OnRestoreInstruction);
follow_branch_action->setEnabled(running && GetBranchFromAddress(addr));
for (auto* action : {copy_address_action, copy_line_action, copy_hex_action, function_action,
ppc_action, insert_blr_action, insert_nop_action, replace_action})
action->setEnabled(running);
for (auto* action : {symbol_rename_action, symbol_size_action, symbol_end_action})
action->setEnabled(has_symbol);
restore_action->setEnabled(running && PowerPC::debug_interface.HasEnabledPatch(addr));
menu->exec(QCursor::pos());
Update();
}
void CodeViewWidget::OnCopyAddress()
{
const u32 addr = GetContextAddress();
QApplication::clipboard()->setText(QStringLiteral("%1").arg(addr, 8, 16, QLatin1Char('0')));
}
void CodeViewWidget::OnCopyCode()
{
const u32 addr = GetContextAddress();
QApplication::clipboard()->setText(
QString::fromStdString(PowerPC::debug_interface.Disassemble(addr)));
}
void CodeViewWidget::OnCopyFunction()
{
const u32 address = GetContextAddress();
const Symbol* symbol = g_symbolDB.GetSymbolFromAddr(address);
if (!symbol)
return;
std::string text = symbol->name + "\r\n";
// we got a function
const u32 start = symbol->address;
const u32 end = start + symbol->size;
for (u32 addr = start; addr != end; addr += 4)
{
const std::string disasm = PowerPC::debug_interface.Disassemble(addr);
text += StringFromFormat("%08x: ", addr) + disasm + "\r\n";
}
QApplication::clipboard()->setText(QString::fromStdString(text));
}
void CodeViewWidget::OnCopyHex()
{
const u32 addr = GetContextAddress();
const u32 instruction = PowerPC::debug_interface.ReadInstruction(addr);
QApplication::clipboard()->setText(
QStringLiteral("%1").arg(instruction, 8, 16, QLatin1Char('0')));
}
void CodeViewWidget::OnRunToHere()
{
const u32 addr = GetContextAddress();
PowerPC::debug_interface.SetBreakpoint(addr);
PowerPC::debug_interface.RunToBreakpoint();
Update();
}
void CodeViewWidget::OnPPCComparison()
{
const u32 addr = GetContextAddress();
emit RequestPPCComparison(addr);
}
void CodeViewWidget::OnAddFunction()
{
const u32 addr = GetContextAddress();
g_symbolDB.AddFunction(addr);
emit SymbolsChanged();
Update();
}
void CodeViewWidget::OnInsertBLR()
{
const u32 addr = GetContextAddress();
ReplaceAddress(addr, ReplaceWith::BLR);
}
void CodeViewWidget::OnInsertNOP()
{
const u32 addr = GetContextAddress();
ReplaceAddress(addr, ReplaceWith::NOP);
}
void CodeViewWidget::OnFollowBranch()
{
const u32 addr = GetContextAddress();
u32 branch_addr = GetBranchFromAddress(addr);
if (!branch_addr)
return;
SetAddress(branch_addr, SetAddressUpdate::WithUpdate);
}
void CodeViewWidget::OnRenameSymbol()
{
const u32 addr = GetContextAddress();
Symbol* symbol = g_symbolDB.GetSymbolFromAddr(addr);
if (!symbol)
return;
bool good;
QString name =
QInputDialog::getText(this, tr("Rename symbol"), tr("Symbol name:"), QLineEdit::Normal,
QString::fromStdString(symbol->name), &good);
if (good && !name.isEmpty())
{
symbol->Rename(name.toStdString());
emit SymbolsChanged();
Update();
}
}
void CodeViewWidget::OnSelectionChanged()
{
if (m_address == PowerPC::ppcState.pc)
{
setStyleSheet(
QStringLiteral("QTableView::item:selected {background-color: #00FF00; color: #000000;}"));
}
else if (!styleSheet().isEmpty())
{
setStyleSheet(QStringLiteral(""));
}
}
void CodeViewWidget::OnSetSymbolSize()
{
const u32 addr = GetContextAddress();
Symbol* symbol = g_symbolDB.GetSymbolFromAddr(addr);
if (!symbol)
return;
bool good;
int size =
QInputDialog::getInt(this, tr("Rename symbol"),
tr("Set symbol size (%1):").arg(QString::fromStdString(symbol->name)),
symbol->size, 1, 0xFFFF, 1, &good);
if (!good)
return;
PPCAnalyst::ReanalyzeFunction(symbol->address, *symbol, size);
emit SymbolsChanged();
Update();
}
void CodeViewWidget::OnSetSymbolEndAddress()
{
const u32 addr = GetContextAddress();
Symbol* symbol = g_symbolDB.GetSymbolFromAddr(addr);
if (!symbol)
return;
bool good;
QString name = QInputDialog::getText(
this, tr("Set symbol end address"),
tr("Symbol (%1) end address:").arg(QString::fromStdString(symbol->name)), QLineEdit::Normal,
QStringLiteral("%1").arg(addr + symbol->size, 8, 16, QLatin1Char('0')), &good);
u32 address = name.toUInt(&good, 16);
if (!good)
return;
PPCAnalyst::ReanalyzeFunction(symbol->address, *symbol, address - symbol->address);
emit SymbolsChanged();
Update();
}
void CodeViewWidget::OnReplaceInstruction()
{
const u32 addr = GetContextAddress();
if (!PowerPC::HostIsInstructionRAMAddress(addr))
return;
const PowerPC::TryReadInstResult read_result = PowerPC::TryReadInstruction(addr);
if (!read_result.valid)
return;
bool good;
QString name = QInputDialog::getText(
this, tr("Change instruction"), tr("New instruction:"), QLineEdit::Normal,
QStringLiteral("%1").arg(read_result.hex, 8, 16, QLatin1Char('0')), &good);
u32 code = name.toUInt(&good, 16);
if (good)
{
PowerPC::debug_interface.UnsetPatch(addr);
PowerPC::debug_interface.SetPatch(addr, code);
Update();
}
}
void CodeViewWidget::OnRestoreInstruction()
{
const u32 addr = GetContextAddress();
PowerPC::debug_interface.UnsetPatch(addr);
Update();
}
void CodeViewWidget::resizeEvent(QResizeEvent*)
{
Update();
}
void CodeViewWidget::keyPressEvent(QKeyEvent* event)
{
switch (event->key())
{
case Qt::Key_Up:
m_address -= 3 * sizeof(u32);
Update();
return;
case Qt::Key_Down:
m_address += 3 * sizeof(u32);
Update();
return;
case Qt::Key_PageUp:
m_address -= rowCount() * sizeof(u32);
Update();
return;
case Qt::Key_PageDown:
m_address += rowCount() * sizeof(u32);
Update();
return;
default:
QWidget::keyPressEvent(event);
break;
}
}
void CodeViewWidget::wheelEvent(QWheelEvent* event)
{
int delta = event->delta() > 0 ? -1 : 1;
m_address += delta * 3 * sizeof(u32);
Update();
}
void CodeViewWidget::mousePressEvent(QMouseEvent* event)
{
auto* item = itemAt(event->pos());
if (item == nullptr)
return;
const u32 addr = item->data(Qt::UserRole).toUInt();
m_context_address = addr;
switch (event->button())
{
case Qt::LeftButton:
if (column(item) == 0)
ToggleBreakpoint();
else
SetAddress(addr, SetAddressUpdate::WithUpdate);
Update();
break;
default:
break;
}
}
void CodeViewWidget::ToggleBreakpoint()
{
if (PowerPC::debug_interface.IsBreakpoint(GetContextAddress()))
PowerPC::breakpoints.Remove(GetContextAddress());
else
PowerPC::breakpoints.Add(GetContextAddress());
emit BreakpointsChanged();
Update();
}
void CodeViewWidget::AddBreakpoint()
{
PowerPC::breakpoints.Add(GetContextAddress());
emit BreakpointsChanged();
Update();
}
u32 CodeViewWidget::GetContextAddress() const
{
return m_context_address;
}