mirror of
https://github.com/RPCSX/rpcsx.git
synced 2025-12-06 07:12:14 +01:00
Fix logic error in callstacks handling code, always set first to false after first iteration. Add explicit check for zero return addresses. Current code validity checks may not check for it properly when it sits on interrupt handler entry point (which may contain valid code). Do not allow 0x3FFF0 to be a back chain address because it needs space for LR save area, only 0x3FFE0 and below satisfy this criteria.
760 lines
23 KiB
C++
760 lines
23 KiB
C++
#include "stdafx.h"
|
|
|
|
#include <map>
|
|
|
|
#include <QVBoxLayout>
|
|
#include <QPushButton>
|
|
#include <QHeaderView>
|
|
#include <QTreeWidgetItem>
|
|
|
|
#include "Emu/IdManager.h"
|
|
#include "Emu/Cell/PPUThread.h"
|
|
#include "Emu/Cell/SPUThread.h"
|
|
#include "Emu/Cell/RawSPUThread.h"
|
|
#include "Emu/Cell/lv2/sys_lwmutex.h"
|
|
#include "Emu/Cell/lv2/sys_lwcond.h"
|
|
#include "Emu/Cell/lv2/sys_mutex.h"
|
|
#include "Emu/Cell/lv2/sys_cond.h"
|
|
#include "Emu/Cell/lv2/sys_semaphore.h"
|
|
#include "Emu/Cell/lv2/sys_event_flag.h"
|
|
#include "Emu/Cell/lv2/sys_rwlock.h"
|
|
#include "Emu/Cell/lv2/sys_prx.h"
|
|
#include "Emu/Cell/lv2/sys_overlay.h"
|
|
#include "Emu/Cell/lv2/sys_memory.h"
|
|
#include "Emu/Cell/lv2/sys_mmapper.h"
|
|
#include "Emu/Cell/lv2/sys_spu.h"
|
|
#include "Emu/Cell/lv2/sys_process.h"
|
|
#include "Emu/Cell/lv2/sys_timer.h"
|
|
#include "Emu/Cell/lv2/sys_rsx.h"
|
|
#include "Emu/Cell/lv2/sys_vm.h"
|
|
#include "Emu/Cell/lv2/sys_net.h"
|
|
#include "Emu/Cell/lv2/sys_fs.h"
|
|
#include "Emu/Cell/Modules/cellSpurs.h"
|
|
|
|
#include "Emu/RSX/RSXThread.h"
|
|
|
|
#include "kernel_explorer.h"
|
|
#include "qt_utils.h"
|
|
|
|
constexpr auto qstr = QString::fromStdString;
|
|
|
|
enum kernel_item_role
|
|
{
|
|
name_role = Qt::UserRole + 0,
|
|
expanded_role = Qt::UserRole + 1,
|
|
type_role = Qt::UserRole + 2,
|
|
id_role = Qt::UserRole + 3,
|
|
};
|
|
|
|
enum kernel_item_type : int
|
|
{
|
|
root,
|
|
node,
|
|
volatile_node,
|
|
solid_node,
|
|
leaf
|
|
};
|
|
|
|
static QTreeWidgetItem* add_child(QTreeWidgetItem *parent, const QString& text, int column, kernel_item_type type)
|
|
{
|
|
if (parent)
|
|
{
|
|
for (int i = 0; i < parent->childCount(); i++)
|
|
{
|
|
if (parent->child(i)->data(0, kernel_item_role::name_role).toString() == text)
|
|
{
|
|
return parent->child(i);
|
|
}
|
|
}
|
|
}
|
|
QTreeWidgetItem* item = gui::utils::add_child(parent, text, column);
|
|
if (item)
|
|
{
|
|
item->setData(0, kernel_item_role::name_role, text);
|
|
item->setData(0, kernel_item_role::type_role, type);
|
|
}
|
|
return item;
|
|
}
|
|
|
|
static QTreeWidgetItem* add_leaf(QTreeWidgetItem *parent, const QString& text, int column = 0)
|
|
{
|
|
return add_child(parent, text, column, kernel_item_type::leaf);
|
|
}
|
|
|
|
static QTreeWidgetItem* add_node(u32 id, QTreeWidgetItem *parent, const QString& text, int column = 0)
|
|
{
|
|
QTreeWidgetItem* node = add_child(parent, text, column, kernel_item_type::node);
|
|
if (node)
|
|
{
|
|
node->setData(0, kernel_item_role::id_role, id);
|
|
}
|
|
return node;
|
|
}
|
|
|
|
static QTreeWidgetItem* find_first_node(QTreeWidget* tree, QTreeWidgetItem *parent, const QString& regexp)
|
|
{
|
|
if (tree && parent)
|
|
{
|
|
for (auto item : tree->findItems(regexp, Qt::MatchFlag::MatchRegExp | Qt::MatchFlag::MatchRecursive))
|
|
{
|
|
if (item->parent() == parent && item->data(0, kernel_item_role::type_role).toInt() != kernel_item_type::leaf)
|
|
{
|
|
return item;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static QTreeWidgetItem* find_node(QTreeWidget* tree, u32 id)
|
|
{
|
|
if (tree)
|
|
{
|
|
for (auto item : tree->findItems(".*", Qt::MatchFlag::MatchRegExp | Qt::MatchFlag::MatchRecursive))
|
|
{
|
|
if (item->data(0, kernel_item_role::type_role).toInt() == kernel_item_type::node &&
|
|
item->data(0, kernel_item_role::id_role).toUInt() == id)
|
|
{
|
|
return item;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static QTreeWidgetItem* add_volatile_node(QTreeWidget* tree, QTreeWidgetItem *parent, const QString& base_text, const QString& text = "", int column = 0)
|
|
{
|
|
QTreeWidgetItem* node = find_first_node(tree, parent, base_text + ".*");
|
|
if (!node)
|
|
{
|
|
node = add_child(parent, base_text, column, kernel_item_type::volatile_node);
|
|
}
|
|
if (node)
|
|
{
|
|
node->setText(0, text.isEmpty() ? base_text : text);
|
|
}
|
|
return node;
|
|
}
|
|
|
|
static QTreeWidgetItem* add_solid_node(QTreeWidget* tree, QTreeWidgetItem *parent, const QString& base_text, const QString& text = "", int column = 0)
|
|
{
|
|
QTreeWidgetItem* node = find_first_node(tree, parent, base_text + ".*");
|
|
if (!node)
|
|
{
|
|
node = add_child(parent, base_text, column, kernel_item_type::solid_node);
|
|
}
|
|
if (node)
|
|
{
|
|
node->setText(0, text.isEmpty() ? base_text : text);
|
|
}
|
|
return node;
|
|
}
|
|
|
|
kernel_explorer::kernel_explorer(QWidget* parent) : QDialog(parent)
|
|
{
|
|
setWindowTitle(tr("Kernel Explorer"));
|
|
setObjectName("kernel_explorer");
|
|
setAttribute(Qt::WA_DeleteOnClose);
|
|
setMinimumSize(QSize(700, 450));
|
|
|
|
QVBoxLayout* vbox_panel = new QVBoxLayout();
|
|
QHBoxLayout* hbox_buttons = new QHBoxLayout();
|
|
QPushButton* button_refresh = new QPushButton(tr("Refresh"), this);
|
|
hbox_buttons->addWidget(button_refresh);
|
|
hbox_buttons->addStretch();
|
|
|
|
m_tree = new QTreeWidget(this);
|
|
m_tree->setBaseSize(QSize(600, 300));
|
|
m_tree->setWindowTitle(tr("Kernel"));
|
|
m_tree->header()->close();
|
|
|
|
// Merge and display everything
|
|
vbox_panel->addSpacing(10);
|
|
vbox_panel->addLayout(hbox_buttons);
|
|
vbox_panel->addSpacing(10);
|
|
vbox_panel->addWidget(m_tree);
|
|
vbox_panel->addSpacing(10);
|
|
setLayout(vbox_panel);
|
|
|
|
// Events
|
|
connect(button_refresh, &QAbstractButton::clicked, this, &kernel_explorer::Update);
|
|
|
|
Update();
|
|
}
|
|
|
|
void kernel_explorer::Update()
|
|
{
|
|
const auto dct = g_fxo->get<lv2_memory_container>();
|
|
|
|
if (!dct)
|
|
{
|
|
m_tree->clear();
|
|
return;
|
|
}
|
|
|
|
static const size_t additional_size = 6;
|
|
|
|
const std::unordered_map<u32, QString> tree_item_names =
|
|
{
|
|
{ process_info , tr("Process Info")},
|
|
|
|
{ SYS_MEM_OBJECT , tr("Shared Memory")},
|
|
{ virtual_memory , tr("Virtual Memory")},
|
|
{ SYS_MUTEX_OBJECT , tr("Mutexes")},
|
|
{ SYS_COND_OBJECT , tr("Condition Variables")},
|
|
{ SYS_RWLOCK_OBJECT , tr("Reader Writer Locks")},
|
|
{ SYS_INTR_TAG_OBJECT , tr("Interrupt Tags")},
|
|
{ SYS_INTR_SERVICE_HANDLE_OBJECT , tr("Interrupt Service Handles")},
|
|
{ SYS_EVENT_QUEUE_OBJECT , tr("Event Queues")},
|
|
{ SYS_EVENT_PORT_OBJECT , tr("Event Ports")},
|
|
{ SYS_TRACE_OBJECT , tr("Traces")},
|
|
{ SYS_SPUIMAGE_OBJECT , tr("SPU Images")},
|
|
{ SYS_PRX_OBJECT , tr("PRX Modules")},
|
|
{ SYS_SPUPORT_OBJECT , tr("SPU Ports")},
|
|
{ SYS_OVERLAY_OBJECT , tr("Overlay Modules")},
|
|
{ SYS_LWMUTEX_OBJECT , tr("Light Weight Mutexes")},
|
|
{ SYS_TIMER_OBJECT , tr("Timers")},
|
|
{ SYS_SEMAPHORE_OBJECT , tr("Semaphores")},
|
|
{ SYS_FS_FD_OBJECT , tr("File Descriptors ?")},
|
|
{ SYS_LWCOND_OBJECT , tr("Light Weight Condition Variables")},
|
|
{ SYS_EVENT_FLAG_OBJECT , tr("Event Flags")},
|
|
|
|
{ memory_containers , tr("Memory Containers")},
|
|
{ ppu_threads , tr("PPU Threads")},
|
|
{ spu_threads , tr("SPU Threads")},
|
|
{ spu_thread_groups , tr("SPU Thread Groups")},
|
|
{ rsx_contexts , tr("RSX Contexts")},
|
|
{ sockets , tr("Sockets")},
|
|
{ file_descriptors , tr("File Descriptors")},
|
|
};
|
|
|
|
QTreeWidgetItem* root = m_tree->topLevelItem(0);
|
|
if (!root)
|
|
{
|
|
root = new QTreeWidgetItem();
|
|
root->setData(0, kernel_item_role::type_role, kernel_item_type::root);
|
|
m_tree->addTopLevelItem(root);
|
|
|
|
for (const auto& [key, name] : tree_item_names)
|
|
{
|
|
add_node(key, root, name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::function<void(QTreeWidgetItem*)> clean_up_tree;
|
|
clean_up_tree = [&clean_up_tree](QTreeWidgetItem* item)
|
|
{
|
|
if (item && item->data(0, kernel_item_role::type_role).toInt() != kernel_item_type::leaf)
|
|
{
|
|
item->setText(0, item->data(0, kernel_item_role::name_role).toString());
|
|
item->setData(0, kernel_item_role::expanded_role, item->isExpanded());
|
|
|
|
for (int i = item->childCount() - 1; i >= 0; i--)
|
|
{
|
|
switch (item->child(i)->data(0, kernel_item_role::type_role).toInt())
|
|
{
|
|
case kernel_item_type::leaf:
|
|
{
|
|
delete item->takeChild(i);
|
|
break;
|
|
}
|
|
case kernel_item_type::volatile_node:
|
|
{
|
|
if (item->child(i)->childCount() <= 0)
|
|
{
|
|
// Cleanup volatile node if it has no children
|
|
delete item->takeChild(i);
|
|
break;
|
|
}
|
|
[[fallthrough]];
|
|
}
|
|
case kernel_item_type::solid_node:
|
|
case kernel_item_type::node:
|
|
case kernel_item_type::root:
|
|
default:
|
|
{
|
|
clean_up_tree(item->child(i));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
clean_up_tree(root);
|
|
}
|
|
|
|
const u32 total_memory_usage = dct->used;
|
|
|
|
root->setText(0, qstr(fmt::format("Process 0x%08x: Total Memory Usage: 0x%x/0x%x (%0.2f/%0.2f MB)", process_getpid(), total_memory_usage, dct->size, 1. * total_memory_usage / (1024 * 1024)
|
|
, 1. * dct->size / (1024 * 1024))));
|
|
|
|
add_solid_node(m_tree, find_node(m_tree, additional_nodes::process_info), qstr(fmt::format("Process Info, Sdk Version: 0x%08x, PPC SEG: %#x, SFO Category: %s", g_ps3_process_info.sdk_ver, g_ps3_process_info.ppc_seg, Emu.GetCat())));
|
|
|
|
idm::select<lv2_obj>([&](u32 id, lv2_obj& obj)
|
|
{
|
|
auto node = find_node(m_tree, id >> 24);
|
|
if (!node)
|
|
{
|
|
return;
|
|
}
|
|
|
|
switch (id >> 24)
|
|
{
|
|
case SYS_MEM_OBJECT:
|
|
{
|
|
auto& mem = static_cast<lv2_memory&>(obj);
|
|
add_leaf(node, qstr(fmt::format("Shared Mem 0x%08x: Size: 0x%x (%0.2f MB), Granularity: %s, Mappings: %u", id, mem.size, mem.size * 1. / (1024 * 1024), mem.align == 0x10000u ? "64K" : "1MB", +mem.counter)));
|
|
break;
|
|
}
|
|
case SYS_MUTEX_OBJECT:
|
|
{
|
|
auto& mutex = static_cast<lv2_mutex&>(obj);
|
|
add_leaf(node, qstr(fmt::format(u8"Mutex 0x%08x: “%s”, %s,%s Owner: %#x, Locks: %u, Key: %#llx, Conds: %u, Wq: %zu", id, lv2_obj::name64(mutex.name), mutex.protocol,
|
|
mutex.recursive == SYS_SYNC_RECURSIVE ? " Recursive," : "", mutex.owner >> 1, +mutex.lock_count, mutex.key, mutex.obj_count.load().cond_count, mutex.sq.size())));
|
|
break;
|
|
}
|
|
case SYS_COND_OBJECT:
|
|
{
|
|
auto& cond = static_cast<lv2_cond&>(obj);
|
|
add_leaf(node, qstr(fmt::format(u8"Cond 0x%08x: “%s”, %s, Mutex: 0x%08x, Key: %#llx, Wq: %u", id, lv2_obj::name64(cond.name), cond.mutex->protocol, cond.mtx_id, cond.key, +cond.waiters)));
|
|
break;
|
|
}
|
|
case SYS_RWLOCK_OBJECT:
|
|
{
|
|
auto& rw = static_cast<lv2_rwlock&>(obj);
|
|
const s64 val = rw.owner;
|
|
add_leaf(node, qstr(fmt::format(u8"RW Lock 0x%08x: “%s”, %s, Owner: %#x(%d), Key: %#llx, Rq: %zu, Wq: %zu", id, lv2_obj::name64(rw.name), rw.protocol,
|
|
std::max<s64>(0, val >> 1), -std::min<s64>(0, val >> 1), rw.key, rw.rq.size(), rw.wq.size())));
|
|
break;
|
|
}
|
|
case SYS_INTR_TAG_OBJECT:
|
|
{
|
|
// auto& tag = static_cast<lv2_int_tag&>(obj);
|
|
add_leaf(node, qstr(fmt::format("Intr Tag 0x%08x", id)));
|
|
break;
|
|
}
|
|
case SYS_INTR_SERVICE_HANDLE_OBJECT:
|
|
{
|
|
// auto& serv = static_cast<lv2_int_serv&>(obj);
|
|
add_leaf(node, qstr(fmt::format("Intr Svc 0x%08x", id)));
|
|
break;
|
|
}
|
|
case SYS_EVENT_QUEUE_OBJECT:
|
|
{
|
|
auto& eq = static_cast<lv2_event_queue&>(obj);
|
|
add_leaf(node, qstr(fmt::format(u8"Event Queue 0x%08x: “%s”, %s, %s, Key: %#llx, Events: %zu/%d, Wq: %zu", id, lv2_obj::name64(eq.name), eq.protocol,
|
|
eq.type == SYS_SPU_QUEUE ? "SPU" : "PPU", eq.key, eq.events.size(), eq.size, eq.sq.size())));
|
|
break;
|
|
}
|
|
case SYS_EVENT_PORT_OBJECT:
|
|
{
|
|
auto& ep = static_cast<lv2_event_port&>(obj);
|
|
add_leaf(node, qstr(fmt::format("Event Port 0x%08x: Name: %#llx, Bound: %s", id, ep.name, lv2_event_queue::check(ep.queue))));
|
|
break;
|
|
}
|
|
case SYS_TRACE_OBJECT:
|
|
{
|
|
add_leaf(node, qstr(fmt::format("Trace 0x%08x", id)));
|
|
break;
|
|
}
|
|
case SYS_SPUIMAGE_OBJECT:
|
|
{
|
|
auto& spi = static_cast<lv2_spu_image&>(obj);
|
|
add_leaf(node, qstr(fmt::format("SPU Image 0x%08x, Entry: 0x%x, Segs: *0x%x, Num Segs: %d", id, spi.e_entry, spi.segs, spi.nsegs)));
|
|
break;
|
|
}
|
|
case SYS_PRX_OBJECT:
|
|
{
|
|
auto& prx = static_cast<lv2_prx&>(obj);
|
|
const u32 addr0 = !prx.segs.empty() ? prx.segs[0].addr : 0;
|
|
|
|
if (!addr0)
|
|
{
|
|
add_leaf(node, qstr(fmt::format("PRX 0x%08x: '%s' (HLE)", id, prx.name)));
|
|
break;
|
|
}
|
|
|
|
const u32 end0 = addr0 + prx.segs[0].size - 1;
|
|
add_leaf(node, qstr(fmt::format("PRX 0x%08x: '%s', Seg0: (0x%x...0x%x)", id, prx.name, addr0, end0)));
|
|
break;
|
|
}
|
|
case SYS_SPUPORT_OBJECT:
|
|
{
|
|
add_leaf(node, qstr(fmt::format("SPU Port 0x%08x", id)));
|
|
break;
|
|
}
|
|
case SYS_OVERLAY_OBJECT:
|
|
{
|
|
auto& ovl = static_cast<lv2_overlay&>(obj);
|
|
add_leaf(node, qstr(fmt::format("OVL 0x%08x: '%s', Seg0: (0x%x...0x%x)", id, ovl.name, ovl.segs[0].addr, ovl.segs[0].addr + ovl.segs[0].size - 1)));
|
|
break;
|
|
}
|
|
case SYS_LWMUTEX_OBJECT:
|
|
{
|
|
auto& lwm = static_cast<lv2_lwmutex&>(obj);
|
|
std::string owner_str = "unknown"; // Either invalid state or the lwmutex control data was moved from
|
|
sys_lwmutex_t lwm_data{};
|
|
|
|
if (lwm.control.try_read(lwm_data))
|
|
{
|
|
switch (const u32 owner = lwm_data.vars.owner)
|
|
{
|
|
case lwmutex_free: owner_str = "free"; break;
|
|
case lwmutex_dead: owner_str = "dead"; break;
|
|
case lwmutex_reserved: owner_str = "reserved"; break;
|
|
default:
|
|
{
|
|
if (idm::check_unlocked<named_thread<ppu_thread>>(owner))
|
|
{
|
|
owner_str = fmt::format("0x%x", owner);
|
|
}
|
|
else
|
|
{
|
|
fmt::append(owner_str, " (0x%x)", owner);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
add_leaf(node, qstr(fmt::format(u8"LWMutex 0x%08x: “%s”, %s, Signal: %#x, Wq: %zu (Couldn't extract control data)", id, lv2_obj::name64(lwm.name), lwm.protocol, +lwm.signaled, lwm.sq.size())));
|
|
break;
|
|
}
|
|
|
|
add_leaf(node, qstr(fmt::format(u8"LWMutex 0x%08x: “%s”, %s,%s Owner: %s, Locks: %u, Signal: %#x, Wq: %zu", id, lv2_obj::name64(lwm.name), lwm.protocol,
|
|
(lwm_data.attribute & SYS_SYNC_RECURSIVE) ? " Recursive," : "", owner_str, lwm_data.recursive_count, +lwm.signaled, lwm.sq.size())));
|
|
break;
|
|
}
|
|
case SYS_TIMER_OBJECT:
|
|
{
|
|
auto& timer = static_cast<lv2_timer&>(obj);
|
|
|
|
sys_timer_information_t info;
|
|
timer.get_information(info);
|
|
|
|
add_leaf(node, qstr(fmt::format("Timer 0x%08x: State: %s, Period: 0x%llx, Next Expire: 0x%llx", id, info.timer_state ? "Running" : "Stopped"
|
|
, info.period, info.next_expire)));
|
|
break;
|
|
}
|
|
case SYS_SEMAPHORE_OBJECT:
|
|
{
|
|
auto& sema = static_cast<lv2_sema&>(obj);
|
|
const auto val = +sema.val;
|
|
add_leaf(node, qstr(fmt::format(u8"Sema 0x%08x: “%s”, %s, Count: %d/%d, Key: %#llx, Wq: %zu", id, lv2_obj::name64(sema.name), sema.protocol,
|
|
std::max<s32>(val, 0), sema.max, sema.key, -std::min<s32>(val, 0))));
|
|
break;
|
|
}
|
|
case SYS_LWCOND_OBJECT:
|
|
{
|
|
auto& lwc = static_cast<lv2_lwcond&>(obj);
|
|
add_leaf(node, qstr(fmt::format(u8"LWCond 0x%08x: “%s”, %s, OG LWMutex: 0x%08x, Wq: %zu", id, lv2_obj::name64(lwc.name), lwc.protocol, lwc.lwid, +lwc.waiters)));
|
|
break;
|
|
}
|
|
case SYS_EVENT_FLAG_OBJECT:
|
|
{
|
|
auto& ef = static_cast<lv2_event_flag&>(obj);
|
|
add_leaf(node, qstr(fmt::format(u8"Event Flag 0x%08x: “%s”, %s, Type: 0x%x, Key: %#llx, Pattern: 0x%llx, Wq: %zu", id, lv2_obj::name64(ef.name), ef.protocol,
|
|
ef.type, ef.key, ef.pattern.load(), +ef.waiters)));
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
add_leaf(node, qstr(fmt::format("Unknown object 0x%08x", id)));
|
|
}
|
|
}
|
|
});
|
|
|
|
idm::select<sys_vm_t>([&](u32 /*id*/, sys_vm_t& vmo)
|
|
{
|
|
const u32 psize = vmo.psize;
|
|
add_leaf(find_node(m_tree, additional_nodes::virtual_memory), qstr(fmt::format("Virtual Mem 0x%08x: Virtual Size: 0x%x (%0.2f MB), Physical Size: 0x%x (%0.2f MB)", vmo.addr
|
|
, vmo.size, vmo.size * 1. / (1024 * 1024), psize, psize * 1. / (1024 * 1024))));
|
|
});
|
|
|
|
idm::select<lv2_socket>([&](u32 id, lv2_socket& sock)
|
|
{
|
|
add_leaf(find_node(m_tree, additional_nodes::sockets), qstr(fmt::format("Socket %u: Type: %s, Family: %s, Wq: %zu", id, sock.type, sock.family, sock.queue.size())));
|
|
});
|
|
|
|
idm::select<lv2_memory_container>([&](u32 id, lv2_memory_container& container)
|
|
{
|
|
const u32 used = container.used;
|
|
add_leaf(find_node(m_tree, additional_nodes::memory_containers), qstr(fmt::format("Memory Container 0x%08x: Used: 0x%x/0x%x (%0.2f/%0.2f MB)", id, used, container.size, used * 1. / (1024 * 1024), container.size * 1. / (1024 * 1024))));
|
|
});
|
|
|
|
idm::select<named_thread<ppu_thread>>([&](u32 id, ppu_thread& ppu)
|
|
{
|
|
const auto func = ppu.last_function;
|
|
add_leaf(find_node(m_tree, additional_nodes::ppu_threads), qstr(fmt::format(u8"PPU 0x%07x: “%s”, PRIO: %d, Joiner: %s, State: %s, %s func: “%s”", id, *ppu.ppu_tname.load(), +ppu.prio, ppu.joiner.load(), ppu.state.load()
|
|
, ppu.current_function ? "In" : "Last", func ? func : "")));
|
|
});
|
|
|
|
idm::select<named_thread<spu_thread>>([&](u32 /*id*/, spu_thread& spu)
|
|
{
|
|
add_leaf(find_node(m_tree, additional_nodes::spu_threads), qstr(fmt::format(u8"SPU 0x%07x: “%s”, State: %s, Type: %s", spu.lv2_id, *spu.spu_tname.load(), spu.state.load(), spu.get_type())));
|
|
});
|
|
|
|
idm::select<lv2_spu_group>([&](u32 id, lv2_spu_group& tg)
|
|
{
|
|
QTreeWidgetItem* spu_tree = add_solid_node(m_tree, find_node(m_tree, additional_nodes::spu_thread_groups), qstr(fmt::format(u8"SPU Group 0x%07x: “%s”, Status = %s, Priority = %d, Type = 0x%x", id, tg.name, tg.run_state.load(), +tg.prio, tg.type)));
|
|
|
|
if (tg.name.ends_with("CellSpursKernelGroup"sv))
|
|
{
|
|
vm::ptr<CellSpurs> pspurs{};
|
|
|
|
for (const auto& thread : tg.threads)
|
|
{
|
|
if (thread)
|
|
{
|
|
// Find SPURS structure address
|
|
const u64 arg = tg.args[thread->index][1];
|
|
|
|
if (!pspurs)
|
|
{
|
|
if (arg < UINT32_MAX && arg % 0x80 == 0 && vm::check_addr(arg, pspurs.size()))
|
|
{
|
|
pspurs.set(static_cast<u32>(arg));
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else if (pspurs.addr() != arg)
|
|
{
|
|
pspurs = {};
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
CellSpurs spurs{};
|
|
|
|
if (pspurs && pspurs.try_read(spurs))
|
|
{
|
|
const QString branch_name = "SPURS";
|
|
const u32 wklEnabled = spurs.wklEnabled;
|
|
QTreeWidgetItem* spurs_tree = add_solid_node(m_tree, spu_tree, branch_name, branch_name + qstr(fmt::format(", Instance: *0x%x, LWMutex: 0x%x, LWCond: 0x%x, wklEnabled: 0x%x"
|
|
, pspurs, spurs.mutex.sleep_queue, spurs.cond.lwcond_queue, wklEnabled)));
|
|
|
|
const u32 signal_mask = u32{spurs.wklSignal1} << 16 | spurs.wklSignal2;
|
|
|
|
for (u32 wid = 0; wid < CELL_SPURS_MAX_WORKLOAD2; wid++)
|
|
{
|
|
if (!(wklEnabled & (0x80000000u >> wid)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const auto state = spurs.wklState(wid).raw();
|
|
|
|
if (state == SPURS_WKL_STATE_NON_EXISTENT)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const u32 ready_count = spurs.readyCount(wid);
|
|
const auto& name = spurs.wklName(wid);
|
|
const u8 evt = spurs.wklEvent(wid);
|
|
const u8 status = spurs.wklStatus(wid);
|
|
const auto has_signal = (signal_mask & (0x80000000u >> wid)) ? "Signalled"sv : "No Signal"sv;
|
|
|
|
QTreeWidgetItem* wkl_tree = add_solid_node(m_tree, spurs_tree, qstr(fmt::format("Work.%u", wid)), qstr(fmt::format("Work.%u, class: %s, %s, %s, Status: %#x, Event: %#x, %s, ReadyCnt: %u", wid, +name.nameClass, +name.nameInstance, state, status, evt, has_signal, ready_count)));
|
|
|
|
auto contention = [&](u8 v)
|
|
{
|
|
if (wid >= CELL_SPURS_MAX_WORKLOAD)
|
|
return (v >> 4);
|
|
else
|
|
return v & 0xf;
|
|
};
|
|
|
|
const auto& winfo = spurs.wklInfo(wid);
|
|
add_leaf(wkl_tree, qstr(fmt::format("Contention: %u/%u (pending: %u), Image: *0x%x (size: 0x%x, arg: 0x%x), Priority (BE64): %016x", contention(spurs.wklCurrentContention[wid % 16])
|
|
, contention(spurs.wklMaxContention[wid % 16]), contention(spurs.wklPendingContention[wid % 16]), +winfo.addr, winfo.size, winfo.arg, std::bit_cast<be_t<u64>>(winfo.priority))));
|
|
}
|
|
|
|
add_leaf(spurs_tree, qstr(fmt::format("Handler Info: PPU0: 0x%x, PPU1: 0x%x, DirtyState: %u, Waiting: %u, Exiting: %u", spurs.ppu0, spurs.ppu1
|
|
, +spurs.handlerDirty, +spurs.handlerWaiting, +spurs.handlerExiting)));
|
|
}
|
|
else
|
|
{
|
|
// TODO: Might be old CellSpurs structure which is smaller
|
|
}
|
|
}
|
|
});
|
|
|
|
QTreeWidgetItem* rsx_context_node = find_node(m_tree, additional_nodes::rsx_contexts);
|
|
|
|
do
|
|
{
|
|
// Currently a single context is supported at a time
|
|
const auto rsx = rsx::get_current_renderer();
|
|
const auto context_info = g_fxo->get<lv2_rsx_config>();
|
|
|
|
if (!rsx || !context_info)
|
|
{
|
|
break;
|
|
}
|
|
|
|
const auto base = context_info->context_base;
|
|
|
|
if (!base)
|
|
{
|
|
break;
|
|
}
|
|
|
|
const QString branch_name = "RSX Context 0x55555555";
|
|
QTreeWidgetItem* rsx_tree = add_solid_node(m_tree, rsx_context_node, branch_name,
|
|
branch_name + qstr(fmt::format(u8", Local Size: %u MB, Base Addr: 0x%x, Device Addr: 0x%x, Handlers: 0x%x", context_info->memory_size >> 20, base, context_info->device_addr, +vm::_ref<RsxDriverInfo>(context_info->driver_info).handlers)));
|
|
|
|
QTreeWidgetItem* io_tree = add_volatile_node(m_tree, rsx_tree, tr("IO-EA Table"));
|
|
QTreeWidgetItem* zc_tree = add_volatile_node(m_tree, rsx_tree, tr("Zcull Bindings"));
|
|
QTreeWidgetItem* db_tree = add_volatile_node(m_tree, rsx_tree, tr("Display Buffers"));
|
|
|
|
decltype(rsx->iomap_table) table;
|
|
decltype(rsx->display_buffers) dbs;
|
|
decltype(rsx->zculls) zcs;
|
|
{
|
|
std::lock_guard lock(context_info->mutex);
|
|
std::memcpy(&table, &rsx->iomap_table, sizeof(table));
|
|
std::memcpy(&dbs, rsx->display_buffers, sizeof(dbs));
|
|
std::memcpy(&zcs, &rsx->zculls, sizeof(zcs));
|
|
}
|
|
|
|
for (u32 i = 0, size_block = 0, first_ea = 0, first_io = 0;;)
|
|
{
|
|
const auto addr = table.get_addr(i << 20);
|
|
|
|
if (addr == umax)
|
|
{
|
|
if (size_block)
|
|
{
|
|
// Print block
|
|
add_leaf(io_tree, qstr(fmt::format("IO: %08x, EA: %08x, Size: %uMB", first_io, first_ea, size_block)));
|
|
}
|
|
|
|
if (i >= 512u)
|
|
{
|
|
break;
|
|
}
|
|
|
|
size_block = 0;
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
const auto old_block_size = size_block++;
|
|
|
|
i++;
|
|
|
|
if (old_block_size)
|
|
{
|
|
if (first_ea + (old_block_size << 20) == addr)
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// Print last block before we continue to a new one
|
|
add_leaf(io_tree, qstr(fmt::format("IO: %08x, EA: %08x, Size: %uMB", first_io, first_ea, old_block_size)));
|
|
size_block = 1;
|
|
first_ea = addr;
|
|
first_io = (i - 1) << 20;
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
first_ea = addr;
|
|
first_io = (i - 1) << 20;
|
|
}
|
|
}
|
|
|
|
for (const auto& zc : zcs)
|
|
{
|
|
if (zc.bound)
|
|
{
|
|
add_leaf(zc_tree, qstr(fmt::format("O: %07x, W: %u, H: %u, Zformat: 0x%x, AAformat: 0x%x, Dir: 0x%x, sFunc: 0x%x, sRef: %02x, sMask: %02x"
|
|
, zc.offset, zc.height, zc.width, zc.zFormat, zc.aaFormat, zc.zcullDir, zc.sFunc, zc.sRef, zc.sMask)));
|
|
}
|
|
}
|
|
|
|
for (const auto& db : dbs)
|
|
{
|
|
if (db.valid())
|
|
{
|
|
add_leaf(db_tree, qstr(fmt::format("Offset: %07x, Width: %u, Height: %u, Pitch: %u"
|
|
, db.offset, db.height, db.width, db.pitch)));
|
|
}
|
|
}
|
|
}
|
|
while (0);
|
|
|
|
idm::select<lv2_fs_object>([&](u32 id, lv2_fs_object& fo)
|
|
{
|
|
add_leaf(find_node(m_tree, additional_nodes::file_descriptors), qstr(fmt::format("FD %u: %s", id, fo.to_string())));
|
|
});
|
|
|
|
std::function<int(QTreeWidgetItem*)> final_touches;
|
|
final_touches = [&final_touches](QTreeWidgetItem* item) -> int
|
|
{
|
|
int visible_children = 0;
|
|
|
|
for (int i = 0; i < item->childCount(); i++)
|
|
{
|
|
auto node = item->child(i);
|
|
|
|
if (!node)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
switch (int type = node->data(0, kernel_item_role::type_role).toInt())
|
|
{
|
|
case kernel_item_type::leaf:
|
|
{
|
|
node->setHidden(false);
|
|
break;
|
|
}
|
|
case kernel_item_type::node:
|
|
case kernel_item_type::solid_node:
|
|
case kernel_item_type::volatile_node:
|
|
{
|
|
const int count = final_touches(node);
|
|
|
|
if (count > 0)
|
|
{
|
|
// Append count
|
|
node->setText(0, node->text(0) + qstr(fmt::format(" (%zu)", count)));
|
|
|
|
// Expand if necessary
|
|
node->setExpanded(node->data(0, kernel_item_role::expanded_role).toBool());
|
|
}
|
|
|
|
// Hide node if it has no children
|
|
node->setHidden(type != kernel_item_type::solid_node && count <= 0);
|
|
break;
|
|
}
|
|
case kernel_item_type::root:
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!node->isHidden())
|
|
{
|
|
visible_children++;
|
|
}
|
|
}
|
|
|
|
return visible_children;
|
|
};
|
|
final_touches(root);
|
|
root->setExpanded(true);
|
|
}
|