Debugger/SPU: Implement SPU Disassembler

This commit is contained in:
Elad Ashkenazi 2024-08-17 13:24:29 +03:00 committed by Elad
parent 7c898c3e4e
commit d6acdc77e0
5 changed files with 189 additions and 12 deletions

View File

@ -65,7 +65,7 @@ extern std::shared_ptr<CPUDisAsm> make_disasm(const cpu_thread* cpu)
} }
debugger_frame::debugger_frame(std::shared_ptr<gui_settings> gui_settings, QWidget *parent) debugger_frame::debugger_frame(std::shared_ptr<gui_settings> gui_settings, QWidget *parent)
: custom_dock_widget(tr("Debugger [Press F1 for help]"), parent) : custom_dock_widget(tr("Debugger [Press F1 for Help]"), parent)
, m_gui_settings(std::move(gui_settings)) , m_gui_settings(std::move(gui_settings))
{ {
setContentsMargins(0, 0, 0, 0); setContentsMargins(0, 0, 0, 0);
@ -177,7 +177,7 @@ debugger_frame::debugger_frame(std::shared_ptr<gui_settings> gui_settings, QWidg
}); });
connect(m_choice_units, QOverload<int>::of(&QComboBox::activated), this, &debugger_frame::UpdateUI); connect(m_choice_units, QOverload<int>::of(&QComboBox::activated), this, &debugger_frame::UpdateUI);
connect(m_choice_units, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &debugger_frame::OnSelectUnit); connect(m_choice_units, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [&](){ m_is_spu_disasm_mode = false; OnSelectUnit(); });
connect(this, &QDockWidget::visibilityChanged, this, &debugger_frame::EnableUpdateTimer); connect(this, &QDockWidget::visibilityChanged, this, &debugger_frame::EnableUpdateTimer);
connect(m_debugger_list, &debugger_list::BreakpointRequested, m_breakpoint_list, &breakpoint_list::HandleBreakpointRequest); connect(m_debugger_list, &debugger_list::BreakpointRequested, m_breakpoint_list, &breakpoint_list::HandleBreakpointRequest);
@ -317,6 +317,7 @@ void debugger_frame::keyPressEvent(QKeyEvent* event)
"\nKeys Alt+S: Capture SPU images of selected SPU or generalized form when used from PPU." "\nKeys Alt+S: Capture SPU images of selected SPU or generalized form when used from PPU."
"\nKeys Alt+S: Launch a memory viewer pointed to the current RSX semaphores location when used from RSX." "\nKeys Alt+S: Launch a memory viewer pointed to the current RSX semaphores location when used from RSX."
"\nKeys Alt+R: Load last saved SPU state capture." "\nKeys Alt+R: Load last saved SPU state capture."
"\nKeys Alt+F5: Show the SPU disassmebler dialog."
"\nKey D: SPU MFC commands logger, MFC debug setting must be enabled." "\nKey D: SPU MFC commands logger, MFC debug setting must be enabled."
"\nKey D: Also PPU calling history logger, interpreter and non-zero call history size must be used." "\nKey D: Also PPU calling history logger, interpreter and non-zero call history size must be used."
"\nKey E: Instruction Editor: click on the instruction you want to modify, then press E." "\nKey E: Instruction Editor: click on the instruction you want to modify, then press E."
@ -484,7 +485,6 @@ void debugger_frame::keyPressEvent(QKeyEvent* event)
} }
}; };
if (cpu->get_class() == thread_class::spu && g_cfg.core.mfc_debug) if (cpu->get_class() == thread_class::spu && g_cfg.core.mfc_debug)
{ {
const u32 max = get_max_allowed(tr("Max MFC cmds logged"), tr("Decimal only, max allowed is %0."), spu_thread::max_mfc_dump_idx); const u32 max = get_max_allowed(tr("Max MFC cmds logged"), tr("Decimal only, max allowed is %0."), spu_thread::max_mfc_dump_idx);
@ -771,6 +771,16 @@ void debugger_frame::keyPressEvent(QKeyEvent* event)
DoStep(false); DoStep(false);
return; return;
} }
case Qt::Key_F5:
{
if (modifiers & Qt::AltModifier)
{
OnSelectSPUDisassembler();
return;
}
break;
}
default: break; default: break;
} }
} }
@ -1030,6 +1040,11 @@ void debugger_frame::UpdateUnitList()
void debugger_frame::OnSelectUnit() void debugger_frame::OnSelectUnit()
{ {
if (m_is_spu_disasm_mode)
{
return;
}
cpu_thread* selected = nullptr; cpu_thread* selected = nullptr;
if (m_emu_state != system_state::stopped) if (m_emu_state != system_state::stopped)
@ -1059,6 +1074,7 @@ void debugger_frame::OnSelectUnit()
m_disasm.reset(); m_disasm.reset();
m_cpu.reset(); m_cpu.reset();
m_rsx = nullptr; m_rsx = nullptr;
m_spu_disasm_memory.reset();
if (selected) if (selected)
{ {
@ -1122,6 +1138,150 @@ void debugger_frame::OnSelectUnit()
UpdateUI(); UpdateUI();
} }
void debugger_frame::OnSelectSPUDisassembler()
{
if (m_spu_disasm_dialog)
{
m_spu_disasm_dialog->move(QCursor::pos());
m_spu_disasm_dialog->show();
m_spu_disasm_dialog->setFocus();
return;
}
m_spu_disasm_dialog = new QDialog(this);
m_spu_disasm_dialog->setWindowTitle(tr("SPU Disassmebler Properties"));
// Panels
QVBoxLayout* vbox_panel(new QVBoxLayout());
QHBoxLayout* hbox_expression_input_panel = new QHBoxLayout();
QHBoxLayout* hbox_button_panel(new QHBoxLayout());
// Address expression input
QLineEdit* source_eal(new QLineEdit(m_spu_disasm_dialog));
QLineEdit* start_pc(new QLineEdit(m_spu_disasm_dialog));
source_eal->setFont(m_mono);
source_eal->setMaxLength(12);
source_eal->setValidator(new QRegularExpressionValidator(QRegularExpression("^(0[xX])?0*[a-fA-F0-9]{0,8}$"), this));
start_pc->setFont(m_mono);
start_pc->setMaxLength(7);
start_pc->setValidator(new QRegularExpressionValidator(QRegularExpression("^(0[xX])?0*[a-fA-F0-9]{0,5}$"), this));
// Ok/Cancel
QPushButton* button_ok = new QPushButton(tr("OK"));
QPushButton* button_cancel = new QPushButton(tr("Cancel"));
hbox_expression_input_panel->addWidget(new QLabel(tr("Source Address: ")));
hbox_expression_input_panel->addWidget(source_eal);
hbox_expression_input_panel->addSpacing(10);
hbox_expression_input_panel->addWidget(new QLabel(tr("Load PC: ")));
hbox_expression_input_panel->addWidget(start_pc);
hbox_button_panel->addWidget(button_ok);
hbox_button_panel->addWidget(button_cancel);
vbox_panel->addLayout(hbox_expression_input_panel);
vbox_panel->addSpacing(8);
vbox_panel->addLayout(hbox_button_panel);
m_spu_disasm_dialog->setLayout(vbox_panel);
const QFont font = source_eal->font();
source_eal->setPlaceholderText(QString::fromStdString(fmt::format("0x%08x", 0)));
start_pc->setPlaceholderText(QString::fromStdString(fmt::format("0x%05x", 0)));
source_eal->setFixedWidth(gui::utils::get_label_width(source_eal->placeholderText(), &font) + 5);
start_pc->setFixedWidth(gui::utils::get_label_width(start_pc->placeholderText(), &font) + 5);
if (m_spu_disasm_origin_eal)
{
source_eal->setText(QString::fromStdString(fmt::format("0x%08x", m_spu_disasm_origin_eal)));
start_pc->setText(QString::fromStdString(fmt::format("0x%05x", m_spu_disasm_pc)));
}
connect(button_ok, &QAbstractButton::clicked, m_spu_disasm_dialog, &QDialog::accept);
connect(button_cancel, &QAbstractButton::clicked, m_spu_disasm_dialog, &QDialog::reject);
m_spu_disasm_dialog->move(QCursor::pos());
m_spu_disasm_dialog->setAttribute(Qt::WA_DeleteOnClose);
connect(m_spu_disasm_dialog, &QDialog::finished, this, [this, source_eal, start_pc](int result)
{
m_spu_disasm_dialog = nullptr;
if (result != QDialog::Accepted)
{
return;
}
const u64 spu_base = EvaluateExpression(start_pc->text());
if (spu_base > SPU_LS_SIZE - 4 || spu_base % 4)
{
return;
}
const u64 spu_addr = EvaluateExpression(source_eal->text());
if (spu_addr == umax || !spu_addr)
{
return;
}
// Try to load as much memory as possible until SPU local memory ends
// Because I don't think there is a need for a size argument
// The user probably does not know the exact size of the SPU code either
u32 spu_size = SPU_LS_SIZE - spu_base;
for (u32 passed = spu_base; passed < SPU_LS_SIZE; passed += 4096)
{
if (!vm::check_addr(spu_addr + passed))
{
if (passed == spu_base)
{
return;
}
spu_size = passed - spu_base - (spu_addr + passed) % 4096;
break;
}
if (4096 > ~(spu_addr + passed))
{
// For overflow
spu_size = std::min<u32>(SPU_LS_SIZE, 0 - spu_addr);
break;
}
}
m_disasm.reset();
m_cpu.reset();
m_rsx = nullptr;
m_spu_disasm_memory = std::make_shared<utils::shm>(SPU_LS_SIZE);
m_spu_disasm_memory->map_self();
m_is_spu_disasm_mode = true;
std::memset(m_spu_disasm_memory->get(), 0, spu_base);
std::memcpy(m_spu_disasm_memory->get() + spu_base, vm::get_super_ptr(spu_addr), spu_size);
std::memset(m_spu_disasm_memory->get() + spu_base + spu_size, 0, SPU_LS_SIZE - (spu_base + spu_size));
m_spu_disasm_pc = spu_base;
m_spu_disasm_origin_eal = spu_addr;
m_disasm = std::make_shared<SPUDisAsm>(cpu_disasm_mode::interpreter, m_spu_disasm_memory->get());
EnableButtons(true);
m_debugger_list->UpdateCPUData(nullptr, m_disasm.get());
m_breakpoint_list->UpdateCPUData(nullptr, m_disasm.get());
ShowPC(true);
DoUpdate();
UpdateUI();
});
m_spu_disasm_dialog->show();
}
void debugger_frame::DoUpdate() void debugger_frame::DoUpdate()
{ {
// Check if we need to disable a step over bp // Check if we need to disable a step over bp
@ -1219,7 +1379,7 @@ void debugger_frame::ShowGotoAddressDialog()
// -1 from get_pc() turns into 0 // -1 from get_pc() turns into 0
const u32 pc = cpu ? utils::align<u32>(cpu->get_pc(), 4) : 0; const u32 pc = cpu ? utils::align<u32>(cpu->get_pc(), 4) : 0;
expression_input->setPlaceholderText(QString("0x%1").arg(pc, 16, 16, QChar('0'))); expression_input->setPlaceholderText(QString("0x%1").arg(pc, 16, 16, QChar('0')));
expression_input->setFixedWidth(gui::utils::get_label_width(expression_input->placeholderText(), &font)); expression_input->setFixedWidth(gui::utils::get_label_width(expression_input->placeholderText(), &font) + 5);
connect(button_ok, &QAbstractButton::clicked, m_goto_dialog, &QDialog::accept); connect(button_ok, &QAbstractButton::clicked, m_goto_dialog, &QDialog::accept);
connect(button_cancel, &QAbstractButton::clicked, m_goto_dialog, &QDialog::reject); connect(button_cancel, &QAbstractButton::clicked, m_goto_dialog, &QDialog::reject);
@ -1329,7 +1489,7 @@ void debugger_frame::ShowPC(bool user_requested)
{ {
const auto cpu0 = get_cpu(); const auto cpu0 = get_cpu();
const u32 pc = (cpu0 ? cpu0->get_pc() : 0); const u32 pc = (cpu0 ? cpu0->get_pc() : (m_is_spu_disasm_mode ? m_spu_disasm_pc : 0));
if (user_requested) if (user_requested)
{ {

View File

@ -26,6 +26,11 @@ namespace rsx
class thread; class thread;
} }
namespace utils
{
class shm;
}
enum class system_state : u32; enum class system_state : u32;
class instruction_editor_dialog; class instruction_editor_dialog;
@ -67,6 +72,10 @@ class debugger_frame : public custom_dock_widget
std::shared_ptr<CPUDisAsm> m_disasm; // Only shared to allow base/derived functionality std::shared_ptr<CPUDisAsm> m_disasm; // Only shared to allow base/derived functionality
std::shared_ptr<cpu_thread> m_cpu; std::shared_ptr<cpu_thread> m_cpu;
rsx::thread* m_rsx = nullptr; rsx::thread* m_rsx = nullptr;
std::shared_ptr<utils::shm> m_spu_disasm_memory;
u32 m_spu_disasm_origin_eal = 0;
u32 m_spu_disasm_pc = 0;
bool m_is_spu_disasm_mode = false;
breakpoint_list* m_breakpoint_list; breakpoint_list* m_breakpoint_list;
breakpoint_handler* m_ppu_breakpoint_handler; breakpoint_handler* m_ppu_breakpoint_handler;
@ -74,6 +83,7 @@ class debugger_frame : public custom_dock_widget
instruction_editor_dialog* m_inst_editor = nullptr; instruction_editor_dialog* m_inst_editor = nullptr;
register_editor_dialog* m_reg_editor = nullptr; register_editor_dialog* m_reg_editor = nullptr;
QDialog* m_goto_dialog = nullptr; QDialog* m_goto_dialog = nullptr;
QDialog* m_spu_disasm_dialog = nullptr;
std::shared_ptr<gui_settings> m_gui_settings; std::shared_ptr<gui_settings> m_gui_settings;
@ -119,6 +129,7 @@ public Q_SLOTS:
private Q_SLOTS: private Q_SLOTS:
void OnSelectUnit(); void OnSelectUnit();
void OnSelectSPUDisassembler();
void ShowPC(bool user_requested = false); void ShowPC(bool user_requested = false);
void EnableUpdateTimer(bool enable) const; void EnableUpdateTimer(bool enable) const;
void RunBtnPress(); void RunBtnPress();

View File

@ -73,7 +73,7 @@ u32 debugger_list::GetStartAddress(u32 address)
const u32 steps = m_item_count / 3; const u32 steps = m_item_count / 3;
const u32 inst_count_jump_on_step = std::min<u32>(steps, 4); const u32 inst_count_jump_on_step = std::min<u32>(steps, 4);
const bool is_spu = m_cpu && m_cpu->get_class() == thread_class::spu; const bool is_spu = IsSpu();
const u32 address_mask = (is_spu ? 0x3fffc : ~3); const u32 address_mask = (is_spu ? 0x3fffc : ~3);
u32 result = address & address_mask; u32 result = address & address_mask;
@ -129,6 +129,11 @@ u32 debugger_list::GetStartAddress(u32 address)
return m_start_addr; return m_start_addr;
} }
bool debugger_list::IsSpu() const
{
return (m_cpu && m_cpu->get_class() == thread_class::spu) || (m_disasm && !m_cpu);
}
void debugger_list::ShowAddress(u32 addr, bool select_addr, bool direct) void debugger_list::ShowAddress(u32 addr, bool select_addr, bool direct)
{ {
const decltype(spu_thread::local_breakpoints)* spu_bps_list{}; const decltype(spu_thread::local_breakpoints)* spu_bps_list{};
@ -190,7 +195,7 @@ void debugger_list::ShowAddress(u32 addr, bool select_addr, bool direct)
} }
} }
if (!m_cpu || !m_disasm || +m_cpu->state + cpu_flag::exit + cpu_flag::wait == +m_cpu->state) if (!m_disasm || (m_cpu && m_cpu->state.all_of(cpu_flag::exit + cpu_flag::wait)))
{ {
for (uint i = 0; i < m_item_count; ++i) for (uint i = 0; i < m_item_count; ++i)
{ {
@ -202,9 +207,9 @@ void debugger_list::ShowAddress(u32 addr, bool select_addr, bool direct)
} }
else else
{ {
const bool is_spu = m_cpu->get_class() == thread_class::spu; const bool is_spu = IsSpu();
const u32 address_limits = (is_spu ? 0x3fffc : ~3); const u32 address_limits = (is_spu ? 0x3fffc : ~3);
const u32 current_pc = m_cpu->get_pc(); const u32 current_pc = (m_cpu ? m_cpu->get_pc() : 0);
m_start_addr &= address_limits; m_start_addr &= address_limits;
pc = m_start_addr; pc = m_start_addr;
@ -238,14 +243,14 @@ void debugger_list::ShowAddress(u32 addr, bool select_addr, bool direct)
list_item->setBackground(default_background); list_item->setBackground(default_background);
} }
if (m_cpu->get_class() == thread_class::ppu && !vm::check_addr(pc, 0)) if (m_cpu && m_cpu->get_class() == thread_class::ppu && !vm::check_addr(pc, 0))
{ {
list_item->setText((IsBreakpoint(pc) ? ">> " : " ") + qstr(fmt::format("[%08x] ?? ?? ?? ??:", pc))); list_item->setText((IsBreakpoint(pc) ? ">> " : " ") + qstr(fmt::format("[%08x] ?? ?? ?? ??:", pc)));
count = 4; count = 4;
continue; continue;
} }
if (m_cpu->get_class() == thread_class::ppu && !vm::check_addr(pc, vm::page_executable)) if (m_cpu && m_cpu->get_class() == thread_class::ppu && !vm::check_addr(pc, vm::page_executable))
{ {
const u32 data = *vm::get_super_ptr<atomic_be_t<u32>>(pc); const u32 data = *vm::get_super_ptr<atomic_be_t<u32>>(pc);
list_item->setText((IsBreakpoint(pc) ? ">> " : " ") + qstr(fmt::format("[%08x] %02x %02x %02x %02x:", pc, list_item->setText((IsBreakpoint(pc) ? ">> " : " ") + qstr(fmt::format("[%08x] %02x %02x %02x %02x:", pc,

View File

@ -52,6 +52,7 @@ private:
* It really upsetted me I had to copy this code to make debugger_list/frame not circularly dependent. * It really upsetted me I had to copy this code to make debugger_list/frame not circularly dependent.
*/ */
u32 GetStartAddress(u32 address); u32 GetStartAddress(u32 address);
bool IsSpu() const;
std::shared_ptr<gui_settings> m_gui_settings; std::shared_ptr<gui_settings> m_gui_settings;

View File

@ -942,7 +942,7 @@ namespace utils
{ {
void* ptr = m_ptr; void* ptr = m_ptr;
if (!ptr) while (!ptr)
{ {
const auto mapped = this->map(nullptr, prot); const auto mapped = this->map(nullptr, prot);