Debugger: Add HW PPU threads view

This commit is contained in:
Elad 2026-03-01 10:07:25 +02:00
parent 93467f1ab9
commit 68602551f7
5 changed files with 156 additions and 31 deletions

View file

@ -1373,7 +1373,7 @@ std::vector<std::pair<u32, u32>> cpu_thread::dump_callstack_list() const
std::string cpu_thread::dump_misc() const
{
return fmt::format("Type: %s; State: %s\n", get_class() == thread_class::ppu ? "PPU" : get_class() == thread_class::spu ? "SPU" : "RSX", state.load());
return fmt::format("%s[0x%x]; State: %s\n", get_class() == thread_class::ppu ? "PPU" : get_class() == thread_class::spu ? "SPU" : "RSX", id, state.load());
}
bool cpu_thread::suspend_work::push(cpu_thread* _this) noexcept

View file

@ -2219,6 +2219,28 @@ void lv2_obj::prepare_for_sleep(cpu_thread& cpu)
cpu_counter::remove(&cpu);
}
ppu_thread* lv2_obj::get_running_ppu(u32 index)
{
usz thread_count = g_cfg.core.ppu_threads;
if (index >= thread_count)
{
return nullptr;
}
auto target = atomic_storage<ppu_thread*>::load(g_ppu);
for (usz cur = 0; target; target = atomic_storage<ppu_thread*>::load(target->next_ppu), cur++)
{
if (cur == index)
{
return target;
}
}
return nullptr;
}
void lv2_obj::notify_all() noexcept
{
for (auto cpu : g_to_notify)

View file

@ -453,6 +453,7 @@ public:
// Can be called before the actual sleep call in order to move it out of mutex scope
static void prepare_for_sleep(cpu_thread& cpu);
static ppu_thread* get_running_ppu(u32 index);
struct notify_all_t
{

View file

@ -16,6 +16,7 @@
#include "Emu/IdManager.h"
#include "Emu/RSX/RSXThread.h"
#include "Emu/RSX/RSXDisAsm.h"
#include "Emu/Cell/lv2/sys_sync.h"
#include "Emu/Cell/PPUAnalyser.h"
#include "Emu/Cell/PPUDisAsm.h"
#include "Emu/Cell/PPUThread.h"
@ -804,12 +805,47 @@ cpu_thread* debugger_frame::get_cpu()
return nullptr;
}
if (u32 cur = m_choice_units->currentIndex(); cur >= m_hw_ppu_idx && cur < g_cfg.core.ppu_threads + m_hw_ppu_idx)
{
reader_lock lock(lv2_obj::g_mutex);
const auto ppu = lv2_obj::get_running_ppu(cur - m_hw_ppu_idx);
if (ppu == m_cpu.get())
{
// Nothing to do
}
else if (ppu)
{
m_cpu = idm::get_unlocked<named_thread<ppu_thread>>(ppu->id);
}
else
{
m_cpu.reset();
}
}
if (!!m_disasm != !!m_cpu)
{
// Fixup for HW PPU viewer
if (m_cpu)
{
m_disasm = make_disasm(m_cpu.get(), m_cpu);
}
else
{
m_disasm.reset();
}
m_debugger_list->UpdateCPUData(m_disasm);
m_breakpoint_list->UpdateCPUData(m_disasm);
}
// Wait flag is raised by the thread itself, acknowledging exit
if (m_cpu)
{
if (m_cpu->state.all_of(cpu_flag::wait + cpu_flag::exit))
{
m_cpu.reset();
return nullptr;
}
@ -823,12 +859,13 @@ cpu_thread* debugger_frame::get_cpu()
{
if (g_fxo->try_get<rsx::thread>() != m_rsx || !m_rsx->ctrl || m_rsx->state.all_of(cpu_flag::wait + cpu_flag::exit))
{
m_rsx = nullptr;
return nullptr;
}
return m_rsx;
}
return m_rsx;
return nullptr;
}
std::function<cpu_thread*()> debugger_frame::make_check_cpu(cpu_thread* cpu, bool unlocked)
@ -922,19 +959,20 @@ std::function<cpu_thread*()> debugger_frame::make_check_cpu(cpu_thread* cpu, boo
void debugger_frame::UpdateUI()
{
const auto cpu = get_cpu();
auto cpu = get_cpu();
// Refresh at a high rate during initialization (looks weird otherwise)
if (m_ui_update_ctr % (cpu || m_ui_update_ctr < 200 || m_debugger_list->m_dirty_flag ? 5 : 50) == 0)
{
// If no change to instruction position happened, update instruction list at 20hz
ShowPC();
ShowPC(false, cpu);
}
if (m_ui_update_ctr % 20 == 0 && !m_thread_list_pending_update)
{
// Update threads list at 5hz (low priority)
UpdateUnitList();
cpu = get_cpu();
}
if (!cpu)
@ -945,12 +983,13 @@ void debugger_frame::UpdateUI()
{
// Update threads list (thread exited)
UpdateUnitList();
cpu = get_cpu();
}
ShowPC();
ShowPC(false, cpu);
m_last_query_state.clear();
m_last_pc = -1;
DoUpdate();
DoUpdate(cpu);
}
}
else if (m_ui_update_ctr % 5 == 0 || m_ui_update_ctr < m_ui_fast_update_permission_deadline)
@ -966,7 +1005,7 @@ void debugger_frame::UpdateUI()
std::memcpy(m_last_query_state.data(), static_cast<void *>(cpu), size_context);
m_last_pc = cia;
DoUpdate();
DoUpdate(cpu);
const bool paused = !!(cpu->state & s_pause_flags);
@ -982,7 +1021,7 @@ void debugger_frame::UpdateUI()
if (m_ui_update_ctr % 5)
{
// Call if it hasn't been called before
ShowPC();
ShowPC(false, cpu);
}
if (is_using_interpreter(cpu->get_class()))
@ -1022,9 +1061,17 @@ void debugger_frame::UpdateUnitList()
}
std::vector<std::pair<QString, std::function<cpu_thread*()>>> cpu_list;
cpu_list.reserve(threads_created >= threads_deleted ? 0 : threads_created - threads_deleted);
cpu_list.reserve(threads_created >= threads_deleted ? threads_created - threads_deleted : 0);
usz reselected_index = umax;
usz hw_ppu_idx = umax;
if (u32 cur = m_choice_units->currentIndex(); cur >= m_hw_ppu_idx && cur < g_cfg.core.ppu_threads + m_hw_ppu_idx)
{
hw_ppu_idx = cur - m_hw_ppu_idx;
}
m_hw_ppu_idx = umax;
const auto on_select = [&](u32 id, cpu_thread& cpu)
{
@ -1033,7 +1080,7 @@ void debugger_frame::UpdateUnitList()
// Space at the end is to pad a gap on the right
cpu_list.emplace_back(QString::fromStdString((id >> 24 == 0x55 ? "RSX[0x55555555]" : cpu.get_name()) + ' '), std::move(func_cpu));
if (old_cpu_ptr == std::addressof(cpu))
if (old_cpu_ptr == std::addressof(cpu) && hw_ppu_idx == umax)
{
reselected_index = cpu_list.size() - 1;
}
@ -1046,6 +1093,40 @@ void debugger_frame::UpdateUnitList()
idm::select<named_thread<ppu_thread>>(on_select, idm::unlocked);
}
m_hw_ppu_idx = cpu_list.size() + 1; // Account for NoThreadString thread
for (u32 i = 0; i < g_cfg.core.ppu_threads + 0u; i++)
{
std::function<cpu_thread*()> get_ppu_at = [index = i, cpu_storage = shared_ptr<named_thread<ppu_thread>>()]() mutable
{
reader_lock lock(lv2_obj::g_mutex);
const auto ppu = lv2_obj::get_running_ppu(index);
if (ppu == cpu_storage.get())
{
// Nothing to do
}
else if (ppu)
{
cpu_storage = idm::get_unlocked<named_thread<ppu_thread>>(ppu->id);
}
else
{
cpu_storage.reset();
}
return cpu_storage.get();
};
if (hw_ppu_idx == i)
{
reselected_index = cpu_list.size();
}
cpu_list.emplace_back(tr("HwPPU[%0]: Hardware PPU Thread #%1").arg(i + 1).arg(i + 1), std::move(get_ppu_at));
}
if (g_fxo->is_init<id_manager::id_map<named_thread<spu_thread>>>())
{
idm::select<named_thread<spu_thread>>(on_select, idm::unlocked);
@ -1114,6 +1195,7 @@ void debugger_frame::OnSelectUnit()
}
cpu_thread* selected = nullptr;
usz hw_ppu_idx = umax;
if (m_emu_state != system_state::stopped)
{
@ -1135,7 +1217,14 @@ void debugger_frame::OnSelectUnit()
if (!selected && !m_rsx && !m_cpu)
{
return;
if (u32 cur = m_choice_units->currentIndex(); cur >= m_hw_ppu_idx && cur < g_cfg.core.ppu_threads + m_hw_ppu_idx)
{
hw_ppu_idx = cur - m_hw_ppu_idx;
}
else
{
return;
}
}
}
@ -1144,6 +1233,15 @@ void debugger_frame::OnSelectUnit()
m_rsx = nullptr;
m_spu_disasm_memory.reset();
if (hw_ppu_idx != umax)
{
if (hw_ppu_idx > 1)
{
// Sample PPU
selected = ::at32(m_threads_info, 1)();
}
}
if (selected)
{
const u32 cpu_id = selected->id;
@ -1198,8 +1296,8 @@ void debugger_frame::OnSelectUnit()
m_debugger_list->UpdateCPUData(m_disasm);
m_breakpoint_list->UpdateCPUData(m_disasm);
ShowPC(true);
DoUpdate();
ShowPC(true, selected);
DoUpdate(selected);
UpdateUI();
}
@ -1339,30 +1437,28 @@ void debugger_frame::OnSelectSPUDisassembler()
m_debugger_list->UpdateCPUData(m_disasm);
m_breakpoint_list->UpdateCPUData(m_disasm);
ShowPC(true);
DoUpdate();
ShowPC(true, nullptr);
DoUpdate(nullptr);
UpdateUI();
});
m_spu_disasm_dialog->show();
}
void debugger_frame::DoUpdate()
void debugger_frame::DoUpdate(cpu_thread* cpu0)
{
// Check if we need to disable a step over bp
if (const auto cpu0 = get_cpu(); cpu0 && m_last_step_over_breakpoint != umax && cpu0->get_pc() == m_last_step_over_breakpoint)
if (cpu0 && m_last_step_over_breakpoint != umax && cpu0->get_pc() == m_last_step_over_breakpoint)
{
m_ppu_breakpoint_handler->RemoveBreakpoint(m_last_step_over_breakpoint);
m_last_step_over_breakpoint = -1;
}
WritePanels();
WritePanels(cpu0);
}
void debugger_frame::WritePanels()
void debugger_frame::WritePanels(cpu_thread* cpu)
{
const auto cpu = get_cpu();
if (!cpu)
{
m_misc_state->clear();
@ -1412,7 +1508,9 @@ void debugger_frame::ShowGotoAddressDialog()
QLineEdit* expression_input(new QLineEdit(m_goto_dialog));
expression_input->setFont(m_mono);
if (const auto thread = get_cpu(); !thread || thread->get_class() != thread_class::spu)
const auto thread = get_cpu();
if (!thread || thread->get_class() != thread_class::spu)
{
expression_input->setValidator(new HexValidator(expression_input));
}
@ -1436,8 +1534,8 @@ void debugger_frame::ShowGotoAddressDialog()
m_goto_dialog->setLayout(vbox_panel);
const auto cpu_check = make_check_cpu(get_cpu());
const auto cpu = cpu_check();
const auto cpu_check = make_check_cpu(thread);
const auto cpu = thread;
const QFont font = expression_input->font();
// -1 from get_pc() turns into 0
@ -1544,9 +1642,12 @@ void debugger_frame::ClearCallStack()
Q_EMIT CallStackUpdateRequested({});
}
void debugger_frame::ShowPC(bool user_requested)
void debugger_frame::ShowPC(bool user_requested, cpu_thread* cpu0)
{
const auto cpu0 = get_cpu();
if (!cpu0)
{
cpu0 = get_cpu();
}
const u32 pc = (cpu0 ? cpu0->get_pc() : (m_is_spu_disasm_mode ? m_spu_disasm_pc : 0));

View file

@ -79,6 +79,7 @@ class debugger_frame : public custom_dock_widget
std::shared_ptr<CPUDisAsm> m_disasm; // Only shared to allow base/derived functionality
shared_ptr<cpu_thread> m_cpu;
rsx::thread* m_rsx = nullptr;
u32 m_hw_ppu_idx = umax;
std::shared_ptr<utils::shm> m_spu_disasm_memory;
u32 m_spu_disasm_origin_eal = 0;
u32 m_spu_disasm_pc = 0;
@ -107,8 +108,8 @@ public:
void UpdateUI();
void UpdateUnitList();
void DoUpdate();
void WritePanels();
void DoUpdate(cpu_thread* cpu0);
void WritePanels(cpu_thread* cpu);
void EnableButtons(bool enable);
void ShowGotoAddressDialog();
void PerformGoToRequest(const QString& text_argument);
@ -138,7 +139,7 @@ private Q_SLOTS:
void OnSelectUnit();
void OnSelectSPUDisassembler();
void OnRegsContextMenu(const QPoint& pos);
void ShowPC(bool user_requested = false);
void ShowPC(bool user_requested = false, cpu_thread* cpu = nullptr);
void EnableUpdateTimer(bool enable) const;
void RunBtnPress();
void RegsShowMemoryViewerAction();