// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: LGPL-3.0+ #include "SymbolTreeNode.h" #include const QVariant& SymbolTreeNode::value() const { return m_value; } const QString& SymbolTreeNode::display_value() const { return m_display_value; } std::optional 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(); 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(&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(&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(); 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(&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(&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(); 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(); 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(&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(&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(); 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(); 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(); QString result; result += "{"; std::vector 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 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& child : children()) if (child->anySymbolsValid(database)) return true; return false; } const SymbolTreeNode* SymbolTreeNode::parent() const { return m_parent; } const std::vector>& SymbolTreeNode::children() const { return m_children; } bool SymbolTreeNode::childrenFetched() const { return m_children_fetched; } void SymbolTreeNode::setChildren(std::vector> new_children) { for (std::unique_ptr& child : new_children) child->m_parent = this; m_children = std::move(new_children); m_children_fetched = true; } void SymbolTreeNode::insertChildren(std::vector> new_children) { for (std::unique_ptr& 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 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& lhs, const std::unique_ptr& 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& child : m_children) child->sortChildrenRecursively(sort_by_if_type_is_known); }