mirror of https://github.com/PCSX2/pcsx2.git
524 lines
13 KiB
C++
524 lines
13 KiB
C++
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
|
// SPDX-License-Identifier: LGPL-3.0+
|
|
|
|
#include "SymbolTreeModel.h"
|
|
|
|
#include <QtWidgets/QApplication>
|
|
#include <QtGui/QBrush>
|
|
#include <QtGui/QPalette>
|
|
|
|
#include "common/Assertions.h"
|
|
|
|
#include "TypeString.h"
|
|
|
|
SymbolTreeModel::SymbolTreeModel(DebugInterface& cpu, QObject* parent)
|
|
: QAbstractItemModel(parent)
|
|
, m_cpu(cpu)
|
|
{
|
|
}
|
|
|
|
QModelIndex SymbolTreeModel::index(int row, int column, const QModelIndex& parent) const
|
|
{
|
|
SymbolTreeNode* parent_node = nodeFromIndex(parent);
|
|
if (!parent_node)
|
|
return QModelIndex();
|
|
|
|
if (row < 0 || row >= (int)parent_node->children().size())
|
|
return QModelIndex();
|
|
|
|
const SymbolTreeNode* child_node = parent_node->children()[row].get();
|
|
if (!child_node)
|
|
return QModelIndex();
|
|
|
|
return createIndex(row, column, child_node);
|
|
}
|
|
|
|
QModelIndex SymbolTreeModel::parent(const QModelIndex& index) const
|
|
{
|
|
if (!index.isValid())
|
|
return QModelIndex();
|
|
|
|
SymbolTreeNode* child_node = nodeFromIndex(index);
|
|
if (!child_node)
|
|
return QModelIndex();
|
|
|
|
const SymbolTreeNode* parent_node = child_node->parent();
|
|
if (!parent_node || parent_node == m_root.get())
|
|
return QModelIndex();
|
|
|
|
return indexFromNode(*parent_node);
|
|
}
|
|
|
|
int SymbolTreeModel::rowCount(const QModelIndex& parent) const
|
|
{
|
|
if (parent.column() > 0)
|
|
return 0;
|
|
|
|
SymbolTreeNode* node = nodeFromIndex(parent);
|
|
if (!node)
|
|
return 0;
|
|
|
|
return (int)node->children().size();
|
|
}
|
|
|
|
int SymbolTreeModel::columnCount(const QModelIndex& parent) const
|
|
{
|
|
return COLUMN_COUNT;
|
|
}
|
|
|
|
bool SymbolTreeModel::hasChildren(const QModelIndex& parent) const
|
|
{
|
|
if (!parent.isValid())
|
|
return true;
|
|
|
|
SymbolTreeNode* parent_node = nodeFromIndex(parent);
|
|
if (!parent_node)
|
|
return true;
|
|
|
|
// If a node doesn't have a type, it can't generate any children, so all the
|
|
// children that will exist must already be there.
|
|
if (!parent_node->type.valid())
|
|
return !parent_node->children().empty();
|
|
|
|
bool result = true;
|
|
m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void {
|
|
const ccc::ast::Node* type = parent_node->type.lookup_node(database);
|
|
if (!type)
|
|
return;
|
|
|
|
result = nodeHasChildren(*type, database);
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
QVariant SymbolTreeModel::data(const QModelIndex& index, int role) const
|
|
{
|
|
if (!index.isValid())
|
|
return QVariant();
|
|
|
|
SymbolTreeNode* node = nodeFromIndex(index);
|
|
if (!node)
|
|
return QVariant();
|
|
|
|
if (role == Qt::ForegroundRole)
|
|
{
|
|
bool active = true;
|
|
|
|
// Gray out the names of symbols that have been overwritten in memory.
|
|
if (index.column() == NAME)
|
|
active = node->matchesMemory();
|
|
|
|
// Gray out the values of variables that are dead.
|
|
if (index.column() == VALUE && node->liveness().has_value())
|
|
active = *node->liveness();
|
|
|
|
QPalette::ColorGroup group = active ? QPalette::Active : QPalette::Disabled;
|
|
return QBrush(QApplication::palette().color(group, QPalette::Text));
|
|
}
|
|
|
|
if (role != Qt::DisplayRole)
|
|
return QVariant();
|
|
|
|
switch (index.column())
|
|
{
|
|
case NAME:
|
|
{
|
|
return node->name;
|
|
}
|
|
case VALUE:
|
|
{
|
|
if (node->tag != SymbolTreeNode::OBJECT)
|
|
return QVariant();
|
|
|
|
return node->display_value();
|
|
}
|
|
case LOCATION:
|
|
{
|
|
return node->location.toString(m_cpu).rightJustified(8);
|
|
}
|
|
case SIZE:
|
|
{
|
|
if (!node->size.has_value())
|
|
return QVariant();
|
|
|
|
return QString::number(*node->size);
|
|
}
|
|
case TYPE:
|
|
{
|
|
QVariant result;
|
|
m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void {
|
|
const ccc::ast::Node* type = node->type.lookup_node(database);
|
|
if (!type)
|
|
return;
|
|
|
|
result = typeToString(type, database);
|
|
});
|
|
|
|
return result;
|
|
}
|
|
case LIVENESS:
|
|
{
|
|
if (!node->liveness().has_value())
|
|
return QVariant();
|
|
|
|
return *node->liveness() ? tr("Alive") : tr("Dead");
|
|
}
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
bool SymbolTreeModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
|
{
|
|
if (!index.isValid())
|
|
return false;
|
|
|
|
SymbolTreeNode* node = nodeFromIndex(index);
|
|
if (!node)
|
|
return false;
|
|
|
|
bool data_changed = false;
|
|
m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void {
|
|
switch (role)
|
|
{
|
|
case EDIT_ROLE:
|
|
data_changed = node->writeToVM(value, m_cpu, database);
|
|
break;
|
|
case UPDATE_FROM_MEMORY_ROLE:
|
|
data_changed = node->readFromVM(m_cpu, database);
|
|
break;
|
|
}
|
|
});
|
|
|
|
if (data_changed)
|
|
emit dataChanged(index.siblingAtColumn(0), index.siblingAtColumn(COLUMN_COUNT - 1));
|
|
|
|
return data_changed;
|
|
}
|
|
|
|
void SymbolTreeModel::fetchMore(const QModelIndex& parent)
|
|
{
|
|
if (!parent.isValid())
|
|
return;
|
|
|
|
SymbolTreeNode* parent_node = nodeFromIndex(parent);
|
|
if (!parent_node || !parent_node->type.valid())
|
|
return;
|
|
|
|
if (!parent_node->children().empty())
|
|
return;
|
|
|
|
std::vector<std::unique_ptr<SymbolTreeNode>> children;
|
|
m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void {
|
|
const ccc::ast::Node* logical_parent_type = parent_node->type.lookup_node(database);
|
|
if (!logical_parent_type)
|
|
return;
|
|
|
|
children = populateChildren(
|
|
parent_node->name, parent_node->location, *logical_parent_type, parent_node->type, m_cpu, database);
|
|
});
|
|
|
|
bool insert_children = !children.empty();
|
|
if (insert_children)
|
|
beginInsertRows(parent, 0, children.size() - 1);
|
|
parent_node->setChildren(std::move(children));
|
|
if (insert_children)
|
|
endInsertRows();
|
|
}
|
|
|
|
bool SymbolTreeModel::canFetchMore(const QModelIndex& parent) const
|
|
{
|
|
if (!parent.isValid())
|
|
return false;
|
|
|
|
SymbolTreeNode* parent_node = nodeFromIndex(parent);
|
|
if (!parent_node || !parent_node->type.valid())
|
|
return false;
|
|
|
|
bool result = false;
|
|
m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void {
|
|
const ccc::ast::Node* parent_type = parent_node->type.lookup_node(database);
|
|
if (!parent_type)
|
|
return;
|
|
|
|
result = nodeHasChildren(*parent_type, database) && !parent_node->childrenFetched();
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
Qt::ItemFlags SymbolTreeModel::flags(const QModelIndex& index) const
|
|
{
|
|
if (!index.isValid())
|
|
return Qt::NoItemFlags;
|
|
|
|
Qt::ItemFlags flags = QAbstractItemModel::flags(index);
|
|
|
|
if (index.column() == LOCATION || index.column() == TYPE || index.column() == VALUE)
|
|
flags |= Qt::ItemIsEditable;
|
|
|
|
return flags;
|
|
}
|
|
|
|
QVariant SymbolTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
{
|
|
if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
|
|
return QVariant();
|
|
|
|
switch (section)
|
|
{
|
|
case NAME:
|
|
return tr("Name");
|
|
case VALUE:
|
|
return tr("Value");
|
|
case LOCATION:
|
|
return tr("Location");
|
|
case SIZE:
|
|
return tr("Size");
|
|
case TYPE:
|
|
return tr("Type");
|
|
case LIVENESS:
|
|
return tr("Liveness");
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
QModelIndex SymbolTreeModel::indexFromNode(const SymbolTreeNode& node) const
|
|
{
|
|
int row = 0;
|
|
if (node.parent())
|
|
{
|
|
for (int i = 0; i < (int)node.parent()->children().size(); i++)
|
|
if (node.parent()->children()[i].get() == &node)
|
|
row = i;
|
|
}
|
|
else
|
|
row = 0;
|
|
|
|
return createIndex(row, 0, &node);
|
|
}
|
|
|
|
SymbolTreeNode* SymbolTreeModel::nodeFromIndex(const QModelIndex& index) const
|
|
{
|
|
if (!index.isValid())
|
|
return m_root.get();
|
|
|
|
SymbolTreeNode* node = static_cast<SymbolTreeNode*>(index.internalPointer());
|
|
if (!node)
|
|
return m_root.get();
|
|
|
|
return node;
|
|
}
|
|
|
|
void SymbolTreeModel::reset(std::unique_ptr<SymbolTreeNode> new_root)
|
|
{
|
|
beginResetModel();
|
|
m_root = std::move(new_root);
|
|
endResetModel();
|
|
}
|
|
|
|
void SymbolTreeModel::resetChildren(QModelIndex index)
|
|
{
|
|
pxAssertRel(index.isValid(), "Invalid model index.");
|
|
|
|
SymbolTreeNode* node = nodeFromIndex(index);
|
|
if (!node || node->tag != SymbolTreeNode::OBJECT)
|
|
return;
|
|
|
|
resetChildrenRecursive(*node);
|
|
}
|
|
|
|
void SymbolTreeModel::resetChildrenRecursive(SymbolTreeNode& node)
|
|
{
|
|
for (const std::unique_ptr<SymbolTreeNode>& child : node.children())
|
|
resetChildrenRecursive(*child);
|
|
|
|
bool remove_rows = !node.children().empty();
|
|
if (remove_rows)
|
|
beginRemoveRows(indexFromNode(node), 0, node.children().size() - 1);
|
|
node.clearChildren();
|
|
if (remove_rows)
|
|
endRemoveRows();
|
|
}
|
|
|
|
bool SymbolTreeModel::needsReset() const
|
|
{
|
|
if (!m_root)
|
|
return true;
|
|
|
|
bool needs_reset = false;
|
|
m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) {
|
|
needs_reset = !m_root->anySymbolsValid(database);
|
|
});
|
|
|
|
return needs_reset;
|
|
}
|
|
|
|
std::optional<QString> SymbolTreeModel::changeTypeTemporarily(QModelIndex index, std::string_view type_string)
|
|
{
|
|
SymbolTreeNode* node = nodeFromIndex(index);
|
|
if (!node)
|
|
return std::nullopt;
|
|
|
|
resetChildren(index);
|
|
|
|
QString error_message;
|
|
m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void {
|
|
std::unique_ptr<ccc::ast::Node> type = stringToType(type_string, database, error_message);
|
|
if (!error_message.isEmpty())
|
|
return;
|
|
|
|
node->temporary_type = std::move(type);
|
|
node->type = ccc::NodeHandle(node->temporary_type.get());
|
|
});
|
|
|
|
setData(index, QVariant(), UPDATE_FROM_MEMORY_ROLE);
|
|
|
|
return error_message;
|
|
}
|
|
|
|
std::optional<QString> SymbolTreeModel::typeFromModelIndexToString(QModelIndex index)
|
|
{
|
|
SymbolTreeNode* node = nodeFromIndex(index);
|
|
if (!node || node->tag != SymbolTreeNode::OBJECT)
|
|
return std::nullopt;
|
|
|
|
QString result;
|
|
m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void {
|
|
const ccc::ast::Node* type = node->type.lookup_node(database);
|
|
if (!type)
|
|
return;
|
|
|
|
result = typeToString(type, database);
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
std::vector<std::unique_ptr<SymbolTreeNode>> SymbolTreeModel::populateChildren(
|
|
const QString& name,
|
|
SymbolTreeLocation location,
|
|
const ccc::ast::Node& logical_type,
|
|
ccc::NodeHandle parent_handle,
|
|
DebugInterface& cpu,
|
|
const ccc::SymbolDatabase& database)
|
|
{
|
|
auto [physical_type, symbol] = logical_type.physical_type(database);
|
|
|
|
// If we went through a type name, we need to make the node handles for the
|
|
// children point to the new symbol instead of the original one.
|
|
if (symbol)
|
|
parent_handle = ccc::NodeHandle(*symbol, nullptr);
|
|
|
|
std::vector<std::unique_ptr<SymbolTreeNode>> children;
|
|
|
|
switch (physical_type->descriptor)
|
|
{
|
|
case ccc::ast::ARRAY:
|
|
{
|
|
const ccc::ast::Array& array = physical_type->as<ccc::ast::Array>();
|
|
|
|
for (s32 i = 0; i < array.element_count; i++)
|
|
{
|
|
SymbolTreeLocation element_location = location.addOffset(i * array.element_type->size_bytes);
|
|
if (element_location.type == SymbolTreeLocation::NONE)
|
|
continue;
|
|
|
|
std::unique_ptr<SymbolTreeNode> element = std::make_unique<SymbolTreeNode>();
|
|
element->name = QString("[%1]").arg(i);
|
|
element->type = parent_handle.handle_for_child(array.element_type.get());
|
|
element->location = element_location;
|
|
children.emplace_back(std::move(element));
|
|
}
|
|
|
|
break;
|
|
}
|
|
case ccc::ast::POINTER_OR_REFERENCE:
|
|
{
|
|
const ccc::ast::PointerOrReference& pointer_or_reference = physical_type->as<ccc::ast::PointerOrReference>();
|
|
|
|
u32 address = location.read32(cpu);
|
|
if (!cpu.isValidAddress(address))
|
|
break;
|
|
|
|
std::unique_ptr<SymbolTreeNode> pointee = std::make_unique<SymbolTreeNode>();
|
|
pointee->name = QString("*%1").arg(name);
|
|
pointee->type = parent_handle.handle_for_child(pointer_or_reference.value_type.get());
|
|
pointee->location = SymbolTreeLocation(SymbolTreeLocation::MEMORY, address);
|
|
children.emplace_back(std::move(pointee));
|
|
|
|
break;
|
|
}
|
|
case ccc::ast::STRUCT_OR_UNION:
|
|
{
|
|
const ccc::ast::StructOrUnion& struct_or_union = physical_type->as<ccc::ast::StructOrUnion>();
|
|
|
|
std::vector<ccc::ast::StructOrUnion::FlatField> fields;
|
|
struct_or_union.flatten_fields(fields, nullptr, database, true);
|
|
|
|
for (const ccc::ast::StructOrUnion::FlatField& field : fields)
|
|
{
|
|
if (symbol)
|
|
parent_handle = ccc::NodeHandle(*symbol, nullptr);
|
|
|
|
SymbolTreeLocation field_location = location.addOffset(field.base_offset + field.node->offset_bytes);
|
|
if (field_location.type == SymbolTreeLocation::NONE)
|
|
continue;
|
|
|
|
std::unique_ptr<SymbolTreeNode> child_node = std::make_unique<SymbolTreeNode>();
|
|
if (!field.node->name.empty())
|
|
child_node->name = QString::fromStdString(field.node->name);
|
|
else
|
|
child_node->name = QString("(anonymous %1)").arg(ccc::ast::node_type_to_string(*field.node));
|
|
child_node->type = parent_handle.handle_for_child(field.node);
|
|
child_node->location = field_location;
|
|
children.emplace_back(std::move(child_node));
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
}
|
|
}
|
|
|
|
for (std::unique_ptr<SymbolTreeNode>& child : children)
|
|
child->readFromVM(cpu, database);
|
|
|
|
return children;
|
|
}
|
|
|
|
bool SymbolTreeModel::nodeHasChildren(const ccc::ast::Node& logical_type, const ccc::SymbolDatabase& database)
|
|
{
|
|
const ccc::ast::Node& type = *logical_type.physical_type(database).first;
|
|
|
|
bool result = false;
|
|
switch (type.descriptor)
|
|
{
|
|
case ccc::ast::ARRAY:
|
|
{
|
|
const ccc::ast::Array& array = type.as<ccc::ast::Array>();
|
|
result = array.element_count > 0;
|
|
break;
|
|
}
|
|
case ccc::ast::POINTER_OR_REFERENCE:
|
|
{
|
|
result = true;
|
|
break;
|
|
}
|
|
case ccc::ast::STRUCT_OR_UNION:
|
|
{
|
|
const ccc::ast::StructOrUnion& struct_or_union = type.as<ccc::ast::StructOrUnion>();
|
|
result = !struct_or_union.base_classes.empty() || !struct_or_union.fields.empty();
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|