mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-07-11 01:34:17 +02:00
79dbc272b8
This adds three new tabs in the debugger: The Globals tab, the Locals tab and the Parameters tab. In addition, it rewrites the Functions tab. All four of these tabs use the new symbol tree widgets and the associated model. This allows the user the inspect complex data structures in memory with full type information. Lastly, new dialogs have been added for creating symbols.
556 lines
15 KiB
C++
556 lines
15 KiB
C++
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
|
// SPDX-License-Identifier: LGPL-3.0+
|
|
|
|
#include "SymbolTreeNode.h"
|
|
|
|
#include <ccc/ast.h>
|
|
|
|
const QVariant& SymbolTreeNode::value() const
|
|
{
|
|
return m_value;
|
|
}
|
|
|
|
const QString& SymbolTreeNode::display_value() const
|
|
{
|
|
return m_display_value;
|
|
}
|
|
|
|
std::optional<bool> SymbolTreeNode::liveness()
|
|
{
|
|
return m_liveness;
|
|
}
|
|
|
|
bool SymbolTreeNode::readFromVM(DebugInterface& cpu, const ccc::SymbolDatabase& database)
|
|
{
|
|
QVariant new_value;
|
|
|
|
const ccc::ast::Node* logical_type = type.lookup_node(database);
|
|
if (logical_type)
|
|
{
|
|
const ccc::ast::Node& physical_type = *logical_type->physical_type(database).first;
|
|
new_value = readValueAsVariant(physical_type, cpu, database);
|
|
}
|
|
|
|
bool data_changed = false;
|
|
|
|
if (new_value != m_value)
|
|
{
|
|
m_value = std::move(new_value);
|
|
data_changed = true;
|
|
}
|
|
|
|
data_changed |= updateDisplayString(cpu, database);
|
|
data_changed |= updateLiveness(cpu);
|
|
|
|
return data_changed;
|
|
}
|
|
|
|
bool SymbolTreeNode::writeToVM(QVariant value, DebugInterface& cpu, const ccc::SymbolDatabase& database)
|
|
{
|
|
bool data_changed = false;
|
|
|
|
if (value != m_value)
|
|
{
|
|
m_value = std::move(value);
|
|
data_changed = true;
|
|
}
|
|
|
|
const ccc::ast::Node* logical_type = type.lookup_node(database);
|
|
if (logical_type)
|
|
{
|
|
const ccc::ast::Node& physical_type = *logical_type->physical_type(database).first;
|
|
writeValueFromVariant(m_value, physical_type, cpu);
|
|
}
|
|
|
|
data_changed |= updateDisplayString(cpu, database);
|
|
data_changed |= updateLiveness(cpu);
|
|
|
|
return data_changed;
|
|
}
|
|
|
|
QVariant SymbolTreeNode::readValueAsVariant(const ccc::ast::Node& physical_type, DebugInterface& cpu, const ccc::SymbolDatabase& database) const
|
|
{
|
|
switch (physical_type.descriptor)
|
|
{
|
|
case ccc::ast::BUILTIN:
|
|
{
|
|
const ccc::ast::BuiltIn& builtIn = physical_type.as<ccc::ast::BuiltIn>();
|
|
switch (builtIn.bclass)
|
|
{
|
|
case ccc::ast::BuiltInClass::UNSIGNED_8:
|
|
return (qulonglong)location.read8(cpu);
|
|
case ccc::ast::BuiltInClass::SIGNED_8:
|
|
return (qlonglong)(s8)location.read8(cpu);
|
|
case ccc::ast::BuiltInClass::UNQUALIFIED_8:
|
|
return (qulonglong)location.read8(cpu);
|
|
case ccc::ast::BuiltInClass::BOOL_8:
|
|
return (bool)location.read8(cpu);
|
|
case ccc::ast::BuiltInClass::UNSIGNED_16:
|
|
return (qulonglong)location.read16(cpu);
|
|
case ccc::ast::BuiltInClass::SIGNED_16:
|
|
return (qlonglong)(s16)location.read16(cpu);
|
|
case ccc::ast::BuiltInClass::UNSIGNED_32:
|
|
return (qulonglong)location.read32(cpu);
|
|
case ccc::ast::BuiltInClass::SIGNED_32:
|
|
return (qlonglong)(s32)location.read32(cpu);
|
|
case ccc::ast::BuiltInClass::FLOAT_32:
|
|
{
|
|
u32 value = location.read32(cpu);
|
|
return *reinterpret_cast<float*>(&value);
|
|
}
|
|
case ccc::ast::BuiltInClass::UNSIGNED_64:
|
|
return (qulonglong)location.read64(cpu);
|
|
case ccc::ast::BuiltInClass::SIGNED_64:
|
|
return (qlonglong)(s64)location.read64(cpu);
|
|
case ccc::ast::BuiltInClass::FLOAT_64:
|
|
{
|
|
u64 value = location.read64(cpu);
|
|
return *reinterpret_cast<double*>(&value);
|
|
}
|
|
default:
|
|
{
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case ccc::ast::ENUM:
|
|
return location.read32(cpu);
|
|
case ccc::ast::POINTER_OR_REFERENCE:
|
|
case ccc::ast::POINTER_TO_DATA_MEMBER:
|
|
return location.read32(cpu);
|
|
default:
|
|
{
|
|
}
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
bool SymbolTreeNode::writeValueFromVariant(QVariant value, const ccc::ast::Node& physical_type, DebugInterface& cpu) const
|
|
{
|
|
switch (physical_type.descriptor)
|
|
{
|
|
case ccc::ast::BUILTIN:
|
|
{
|
|
const ccc::ast::BuiltIn& built_in = physical_type.as<ccc::ast::BuiltIn>();
|
|
|
|
switch (built_in.bclass)
|
|
{
|
|
case ccc::ast::BuiltInClass::UNSIGNED_8:
|
|
location.write8((u8)value.toULongLong(), cpu);
|
|
break;
|
|
case ccc::ast::BuiltInClass::SIGNED_8:
|
|
location.write8((u8)(s8)value.toLongLong(), cpu);
|
|
break;
|
|
case ccc::ast::BuiltInClass::UNQUALIFIED_8:
|
|
location.write8((u8)value.toULongLong(), cpu);
|
|
break;
|
|
case ccc::ast::BuiltInClass::BOOL_8:
|
|
location.write8((u8)value.toBool(), cpu);
|
|
break;
|
|
case ccc::ast::BuiltInClass::UNSIGNED_16:
|
|
location.write16((u16)value.toULongLong(), cpu);
|
|
break;
|
|
case ccc::ast::BuiltInClass::SIGNED_16:
|
|
location.write16((u16)(s16)value.toLongLong(), cpu);
|
|
break;
|
|
case ccc::ast::BuiltInClass::UNSIGNED_32:
|
|
location.write32((u32)value.toULongLong(), cpu);
|
|
break;
|
|
case ccc::ast::BuiltInClass::SIGNED_32:
|
|
location.write32((u32)(s32)value.toLongLong(), cpu);
|
|
break;
|
|
case ccc::ast::BuiltInClass::FLOAT_32:
|
|
{
|
|
float f = value.toFloat();
|
|
location.write32(*reinterpret_cast<u32*>(&f), cpu);
|
|
break;
|
|
}
|
|
case ccc::ast::BuiltInClass::UNSIGNED_64:
|
|
location.write64((u64)value.toULongLong(), cpu);
|
|
break;
|
|
case ccc::ast::BuiltInClass::SIGNED_64:
|
|
location.write64((u64)(s64)value.toLongLong(), cpu);
|
|
break;
|
|
case ccc::ast::BuiltInClass::FLOAT_64:
|
|
{
|
|
double d = value.toDouble();
|
|
location.write64(*reinterpret_cast<u64*>(&d), cpu);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case ccc::ast::ENUM:
|
|
location.write32((u32)value.toULongLong(), cpu);
|
|
break;
|
|
case ccc::ast::POINTER_OR_REFERENCE:
|
|
case ccc::ast::POINTER_TO_DATA_MEMBER:
|
|
location.write32((u32)value.toULongLong(), cpu);
|
|
break;
|
|
default:
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SymbolTreeNode::updateDisplayString(DebugInterface& cpu, const ccc::SymbolDatabase& database)
|
|
{
|
|
QString result;
|
|
|
|
const ccc::ast::Node* logical_type = type.lookup_node(database);
|
|
if (logical_type)
|
|
{
|
|
const ccc::ast::Node& physical_type = *logical_type->physical_type(database).first;
|
|
result = generateDisplayString(physical_type, cpu, database, 0);
|
|
}
|
|
|
|
if (result.isEmpty())
|
|
{
|
|
// We don't know how to display objects of this type, so just show the
|
|
// first 4 bytes of it as a hex dump.
|
|
u32 value = location.read32(cpu);
|
|
result = QString("%1 %2 %3 %4")
|
|
.arg(value & 0xff, 2, 16, QChar('0'))
|
|
.arg((value >> 8) & 0xff, 2, 16, QChar('0'))
|
|
.arg((value >> 16) & 0xff, 2, 16, QChar('0'))
|
|
.arg((value >> 24) & 0xff, 2, 16, QChar('0'));
|
|
}
|
|
|
|
if (result == m_display_value)
|
|
return false;
|
|
|
|
m_display_value = std::move(result);
|
|
|
|
return true;
|
|
}
|
|
|
|
QString SymbolTreeNode::generateDisplayString(
|
|
const ccc::ast::Node& physical_type, DebugInterface& cpu, const ccc::SymbolDatabase& database, s32 depth) const
|
|
{
|
|
s32 max_elements_to_display = 0;
|
|
switch (depth)
|
|
{
|
|
case 0:
|
|
max_elements_to_display = 8;
|
|
break;
|
|
case 1:
|
|
max_elements_to_display = 2;
|
|
break;
|
|
}
|
|
|
|
switch (physical_type.descriptor)
|
|
{
|
|
case ccc::ast::ARRAY:
|
|
{
|
|
const ccc::ast::Array& array = physical_type.as<ccc::ast::Array>();
|
|
const ccc::ast::Node& element_type = *array.element_type->physical_type(database).first;
|
|
|
|
if (element_type.name == "char" && location.type == SymbolTreeLocation::MEMORY)
|
|
{
|
|
char* string = cpu.stringFromPointer(location.address);
|
|
if (string)
|
|
return QString("\"%1\"").arg(string);
|
|
}
|
|
|
|
QString result;
|
|
result += "{";
|
|
|
|
s32 elements_to_display = std::min(array.element_count, max_elements_to_display);
|
|
for (s32 i = 0; i < elements_to_display; i++)
|
|
{
|
|
SymbolTreeNode node;
|
|
node.location = location.addOffset(i * array.element_type->size_bytes);
|
|
|
|
QString element = node.generateDisplayString(element_type, cpu, database, depth + 1);
|
|
if (element.isEmpty())
|
|
element = QString("(%1)").arg(ccc::ast::node_type_to_string(element_type));
|
|
result += element;
|
|
|
|
if (i + 1 != array.element_count)
|
|
result += ",";
|
|
}
|
|
|
|
if (elements_to_display != array.element_count)
|
|
result += "...";
|
|
|
|
result += "}";
|
|
return result;
|
|
}
|
|
case ccc::ast::BUILTIN:
|
|
{
|
|
const ccc::ast::BuiltIn& builtIn = physical_type.as<ccc::ast::BuiltIn>();
|
|
|
|
QString result;
|
|
switch (builtIn.bclass)
|
|
{
|
|
case ccc::ast::BuiltInClass::UNSIGNED_8:
|
|
result = QString::number(location.read8(cpu));
|
|
break;
|
|
case ccc::ast::BuiltInClass::SIGNED_8:
|
|
result = QString::number((s8)location.read8(cpu));
|
|
break;
|
|
case ccc::ast::BuiltInClass::UNQUALIFIED_8:
|
|
result = QString::number(location.read8(cpu));
|
|
break;
|
|
case ccc::ast::BuiltInClass::BOOL_8:
|
|
result = location.read8(cpu) ? "true" : "false";
|
|
break;
|
|
case ccc::ast::BuiltInClass::UNSIGNED_16:
|
|
result = QString::number(location.read16(cpu));
|
|
break;
|
|
case ccc::ast::BuiltInClass::SIGNED_16:
|
|
result = QString::number((s16)location.read16(cpu));
|
|
break;
|
|
case ccc::ast::BuiltInClass::UNSIGNED_32:
|
|
result = QString::number(location.read32(cpu));
|
|
break;
|
|
case ccc::ast::BuiltInClass::SIGNED_32:
|
|
result = QString::number((s32)location.read32(cpu));
|
|
break;
|
|
case ccc::ast::BuiltInClass::FLOAT_32:
|
|
{
|
|
u32 value = location.read32(cpu);
|
|
result = QString::number(*reinterpret_cast<float*>(&value));
|
|
break;
|
|
}
|
|
case ccc::ast::BuiltInClass::UNSIGNED_64:
|
|
result = QString::number(location.read64(cpu));
|
|
break;
|
|
case ccc::ast::BuiltInClass::SIGNED_64:
|
|
result = QString::number((s64)location.read64(cpu));
|
|
break;
|
|
case ccc::ast::BuiltInClass::FLOAT_64:
|
|
{
|
|
u64 value = location.read64(cpu);
|
|
result = QString::number(*reinterpret_cast<double*>(&value));
|
|
break;
|
|
}
|
|
case ccc::ast::BuiltInClass::UNSIGNED_128:
|
|
case ccc::ast::BuiltInClass::SIGNED_128:
|
|
case ccc::ast::BuiltInClass::UNQUALIFIED_128:
|
|
case ccc::ast::BuiltInClass::FLOAT_128:
|
|
{
|
|
if (depth > 0)
|
|
{
|
|
result = "(128-bit value)";
|
|
break;
|
|
}
|
|
|
|
for (s32 i = 0; i < 16; i++)
|
|
{
|
|
u8 value = location.addOffset(i).read8(cpu);
|
|
result += QString("%1 ").arg(value, 2, 16, QChar('0'));
|
|
if ((i + 1) % 4 == 0)
|
|
result += " ";
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
}
|
|
}
|
|
|
|
if (result.isEmpty())
|
|
break;
|
|
|
|
if (builtIn.name == "char")
|
|
{
|
|
char c = location.read8(cpu);
|
|
if (QChar::fromLatin1(c).isPrint())
|
|
{
|
|
if (depth == 0)
|
|
result = result.leftJustified(3);
|
|
result += QString(" '%1'").arg(c);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
case ccc::ast::ENUM:
|
|
{
|
|
s32 value = (s32)location.read32(cpu);
|
|
const auto& enum_type = physical_type.as<ccc::ast::Enum>();
|
|
for (auto [test_value, name] : enum_type.constants)
|
|
{
|
|
if (test_value == value)
|
|
return QString::fromStdString(name);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case ccc::ast::POINTER_OR_REFERENCE:
|
|
{
|
|
const auto& pointer_or_reference = physical_type.as<ccc::ast::PointerOrReference>();
|
|
const ccc::ast::Node& value_type =
|
|
*pointer_or_reference.value_type->physical_type(database).first;
|
|
|
|
u32 address = location.read32(cpu);
|
|
if (address == 0)
|
|
return "NULL";
|
|
|
|
QString result = QString::number(address, 16);
|
|
|
|
if (pointer_or_reference.is_pointer && value_type.name == "char")
|
|
{
|
|
const char* string = cpu.stringFromPointer(address);
|
|
if (string)
|
|
result += QString(" \"%1\"").arg(string);
|
|
}
|
|
else if (depth == 0)
|
|
{
|
|
QString pointee = generateDisplayString(value_type, cpu, database, depth + 1);
|
|
if (!pointee.isEmpty())
|
|
result += QString(" -> %1").arg(pointee);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
case ccc::ast::POINTER_TO_DATA_MEMBER:
|
|
{
|
|
return QString::number(location.read32(cpu), 16);
|
|
}
|
|
case ccc::ast::STRUCT_OR_UNION:
|
|
{
|
|
const ccc::ast::StructOrUnion& struct_or_union = physical_type.as<ccc::ast::StructOrUnion>();
|
|
|
|
QString result;
|
|
result += "{";
|
|
|
|
std::vector<ccc::ast::StructOrUnion::FlatField> fields;
|
|
bool all_fields = struct_or_union.flatten_fields(fields, nullptr, database, true, 0, max_elements_to_display);
|
|
|
|
for (size_t i = 0; i < fields.size(); i++)
|
|
{
|
|
const ccc::ast::StructOrUnion::FlatField& field = fields[i];
|
|
|
|
SymbolTreeNode node;
|
|
node.location = location.addOffset(field.base_offset + field.node->offset_bytes);
|
|
|
|
const ccc::ast::Node& field_type = *field.node->physical_type(database).first;
|
|
QString field_value = node.generateDisplayString(field_type, cpu, database, depth + 1);
|
|
if (field_value.isEmpty())
|
|
field_value = QString("(%1)").arg(ccc::ast::node_type_to_string(field_type));
|
|
|
|
QString field_name = QString::fromStdString(field.node->name);
|
|
result += QString(".%1=%2").arg(field_name).arg(field_value);
|
|
|
|
if (i + 1 != fields.size() || !all_fields)
|
|
result += ",";
|
|
}
|
|
|
|
if (!all_fields)
|
|
result += "...";
|
|
|
|
result += "}";
|
|
return result;
|
|
}
|
|
default:
|
|
{
|
|
}
|
|
}
|
|
|
|
return QString();
|
|
}
|
|
|
|
bool SymbolTreeNode::updateLiveness(DebugInterface& cpu)
|
|
{
|
|
std::optional<bool> new_liveness;
|
|
if (live_range.low.valid() && live_range.high.valid())
|
|
{
|
|
u32 pc = cpu.getPC();
|
|
new_liveness = pc >= live_range.low && pc < live_range.high;
|
|
}
|
|
|
|
if (new_liveness == m_liveness)
|
|
return false;
|
|
|
|
m_liveness = new_liveness;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SymbolTreeNode::anySymbolsValid(const ccc::SymbolDatabase& database) const
|
|
{
|
|
if (symbol.lookup_symbol(database))
|
|
return true;
|
|
|
|
for (const std::unique_ptr<SymbolTreeNode>& child : children())
|
|
if (child->anySymbolsValid(database))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
const SymbolTreeNode* SymbolTreeNode::parent() const
|
|
{
|
|
return m_parent;
|
|
}
|
|
|
|
const std::vector<std::unique_ptr<SymbolTreeNode>>& SymbolTreeNode::children() const
|
|
{
|
|
return m_children;
|
|
}
|
|
|
|
bool SymbolTreeNode::childrenFetched() const
|
|
{
|
|
return m_children_fetched;
|
|
}
|
|
|
|
void SymbolTreeNode::setChildren(std::vector<std::unique_ptr<SymbolTreeNode>> new_children)
|
|
{
|
|
for (std::unique_ptr<SymbolTreeNode>& child : new_children)
|
|
child->m_parent = this;
|
|
m_children = std::move(new_children);
|
|
m_children_fetched = true;
|
|
}
|
|
|
|
void SymbolTreeNode::insertChildren(std::vector<std::unique_ptr<SymbolTreeNode>> new_children)
|
|
{
|
|
for (std::unique_ptr<SymbolTreeNode>& child : new_children)
|
|
child->m_parent = this;
|
|
m_children.insert(m_children.end(),
|
|
std::make_move_iterator(new_children.begin()),
|
|
std::make_move_iterator(new_children.end()));
|
|
m_children_fetched = true;
|
|
}
|
|
|
|
void SymbolTreeNode::emplaceChild(std::unique_ptr<SymbolTreeNode> new_child)
|
|
{
|
|
new_child->m_parent = this;
|
|
m_children.emplace_back(std::move(new_child));
|
|
m_children_fetched = true;
|
|
}
|
|
|
|
void SymbolTreeNode::clearChildren()
|
|
{
|
|
m_children.clear();
|
|
m_children_fetched = false;
|
|
}
|
|
|
|
void SymbolTreeNode::sortChildrenRecursively(bool sort_by_if_type_is_known)
|
|
{
|
|
auto comparator = [&](const std::unique_ptr<SymbolTreeNode>& lhs, const std::unique_ptr<SymbolTreeNode>& rhs) -> bool {
|
|
if (lhs->tag != rhs->tag)
|
|
return lhs->tag < rhs->tag;
|
|
if (sort_by_if_type_is_known && lhs->type.valid() != rhs->type.valid())
|
|
return lhs->type.valid() > rhs->type.valid();
|
|
if (lhs->location != rhs->location)
|
|
return lhs->location < rhs->location;
|
|
return lhs->name < rhs->name;
|
|
};
|
|
|
|
std::sort(m_children.begin(), m_children.end(), comparator);
|
|
|
|
for (std::unique_ptr<SymbolTreeNode>& child : m_children)
|
|
child->sortChildrenRecursively(sort_by_if_type_is_known);
|
|
}
|