Debugger: Allow strings to be edited from the symbol trees

This commit is contained in:
chaoticgd
2026-06-03 11:08:39 +01:00
committed by lightningterror
parent 6834086c37
commit 1ae2a96747
12 changed files with 191 additions and 75 deletions
+39
View File
@@ -34,6 +34,34 @@ Value MemoryInterface::Read(u32 address, bool* valid)
return Value(0);
}
std::optional<std::string> MemoryInterface::ReadString(u32 address, u32 max_size, ReadStringFlags flags)
{
std::string string;
for (u32 i = 0; i < max_size; i++)
{
bool valid;
char c = Read8(address + i, &valid);
if (!valid)
return std::nullopt;
if (c == '\0')
return string;
else if (!(flags & ALLOW_NON_PRINTABLE_CHARACTERS) && (c < ' ' || c > '~'))
return std::nullopt;
string += c;
}
if (flags & ALLOW_LONG_STRINGS)
{
string += '~';
return string;
}
return std::nullopt;
}
template <MemoryAccessType Value>
bool MemoryInterface::Write(u32 address, Value value)
{
@@ -62,6 +90,17 @@ bool MemoryInterface::Write(u32 address, Value value)
return false;
}
bool MemoryInterface::WriteString(u32 address, std::string_view string)
{
if (!WriteBytes(address, string.data(), static_cast<u32>(string.size())))
return false;
if (!Write8(address + static_cast<u32>(string.size()), '\0'))
return false;
return true;
}
bool MemoryInterface::IdempotentWrite8(u32 address, u8 value)
{
bool valid;
+16 -2
View File
@@ -5,6 +5,9 @@
#include "Pcsx2Types.h"
#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
template <typename Value>
@@ -31,16 +34,27 @@ public:
template <MemoryAccessType Value>
Value Read(u32 address, bool* valid = nullptr);
enum ReadStringFlags
{
NO_FLAGS = 0,
ALLOW_LONG_STRINGS = 1 << 0,
ALLOW_NON_PRINTABLE_CHARACTERS = 1 << 1
};
std::optional<std::string> ReadString(u32 address, u32 max_size = 256, ReadStringFlags flags = NO_FLAGS);
virtual bool Write8(u32 address, u8 value) = 0;
virtual bool Write16(u32 address, u16 value) = 0;
virtual bool Write32(u32 address, u32 value) = 0;
virtual bool Write64(u32 address, u64 value) = 0;
virtual bool Write128(u32 address, u128 value) = 0;
virtual bool WriteBytes(u32 address, void* src, u32 size) = 0;
virtual bool WriteBytes(u32 address, const void* src, u32 size) = 0;
template <MemoryAccessType Value>
bool Write(u32 address, Value value);
bool WriteString(u32 address, std::string_view string);
bool IdempotentWrite8(u32 address, u8 value);
bool IdempotentWrite16(u32 address, u16 value);
bool IdempotentWrite32(u32 address, u32 value);
@@ -51,5 +65,5 @@ public:
template <MemoryAccessType Value>
bool IdempotentWrite(u32 address, Value value);
virtual bool CompareBytes(u32 address, void* src, u32 size) = 0;
virtual bool CompareBytes(u32 address, const void* src, u32 size) = 0;
};
@@ -7,6 +7,8 @@
#include "Debugger/SymbolTree/SymbolTreeModel.h"
#include "Debugger/SymbolTree/TypeString.h"
#include "common/StringUtil.h"
#include <QtWidgets/QCheckBox>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QDoubleSpinBox>
@@ -44,12 +46,28 @@ QWidget* SymbolTreeValueDelegate::createEditor(QWidget* parent, const QStyleOpti
const ccc::ast::Node& physical_type = *logical_type->physical_type(database).first;
QVariant value = node->readValueAsVariant(physical_type, m_cpu, database);
const ccc::ast::Node& type = *logical_type->physical_type(database).first;
switch (type.descriptor)
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 (array.element_count > 0 &&
element_type.name == "char" &&
node->location.type == SymbolTreeLocation::MEMORY &&
!value.isNull())
{
QLineEdit* editor = new QLineEdit(parent);
editor->setText(value.toString());
result = editor;
}
break;
}
case ccc::ast::BUILTIN:
{
const ccc::ast::BuiltIn& builtin = type.as<ccc::ast::BuiltIn>();
const ccc::ast::BuiltIn& builtin = physical_type.as<ccc::ast::BuiltIn>();
switch (builtin.bclass)
{
@@ -111,7 +129,7 @@ QWidget* SymbolTreeValueDelegate::createEditor(QWidget* parent, const QStyleOpti
}
case ccc::ast::ENUM:
{
const ccc::ast::Enum& enumeration = type.as<ccc::ast::Enum>();
const ccc::ast::Enum& enumeration = physical_type.as<ccc::ast::Enum>();
QComboBox* combo_box = new QComboBox(parent);
bool named = false;
@@ -186,12 +204,30 @@ void SymbolTreeValueDelegate::setModelData(QWidget* editor, QAbstractItemModel*
if (!logical_type)
return;
const ccc::ast::Node& type = *logical_type->physical_type(database).first;
switch (type.descriptor)
const ccc::ast::Node& physical_type = *logical_type->physical_type(database).first;
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;
QLineEdit* line_edit = qobject_cast<QLineEdit*>(editor);
if (array.element_count > 0 &&
element_type.name == "char" &&
node->location.type == SymbolTreeLocation::MEMORY &&
line_edit)
{
value = line_edit->text();
}
break;
}
case ccc::ast::BUILTIN:
{
const ccc::ast::BuiltIn& builtin = type.as<ccc::ast::BuiltIn>();
const ccc::ast::BuiltIn& builtin = physical_type.as<ccc::ast::BuiltIn>();
switch (builtin.bclass)
{
+60 -10
View File
@@ -67,7 +67,7 @@ bool SymbolTreeNode::writeToVM(
if (logical_type)
{
const ccc::ast::Node& physical_type = *logical_type->physical_type(database).first;
writeValueFromVariant(m_value, physical_type, cpu);
writeValueFromVariant(m_value, physical_type, cpu, database);
}
data_changed |= updateDisplayString(cpu, database, display_options);
@@ -76,10 +76,29 @@ bool SymbolTreeNode::writeToVM(
return data_changed;
}
QVariant SymbolTreeNode::readValueAsVariant(const ccc::ast::Node& physical_type, DebugInterface& cpu, const ccc::SymbolDatabase& database) const
QVariant SymbolTreeNode::readValueAsVariant(
const ccc::ast::Node& physical_type,
DebugInterface& cpu,
const ccc::SymbolDatabase& database) const
{
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 (array.element_count > 0 &&
element_type.name == "char" &&
location.type == SymbolTreeLocation::MEMORY)
{
std::optional<std::string> string = cpu.ReadString(location.address, 256);
if (string.has_value())
return QString::fromStdString(*string);
}
break;
}
case ccc::ast::BUILTIN:
{
const ccc::ast::BuiltIn& builtIn = physical_type.as<ccc::ast::BuiltIn>();
@@ -134,10 +153,36 @@ QVariant SymbolTreeNode::readValueAsVariant(const ccc::ast::Node& physical_type,
return QVariant();
}
bool SymbolTreeNode::writeValueFromVariant(QVariant value, const ccc::ast::Node& physical_type, DebugInterface& cpu) const
bool SymbolTreeNode::writeValueFromVariant(
QVariant value,
const ccc::ast::Node& physical_type,
DebugInterface& cpu,
const ccc::SymbolDatabase& database) const
{
if (value.isNull())
return false;
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 (array.element_count > 0 &&
element_type.name == "char" &&
location.type == SymbolTreeLocation::MEMORY)
{
QByteArray byte_array = value.toString().toLatin1();
std::string_view view(
byte_array.data(),
std::min(static_cast<s32>(byte_array.size()), array.element_count - 1));
cpu.WriteString(location.address, view);
break;
}
return false;
}
case ccc::ast::BUILTIN:
{
const ccc::ast::BuiltIn& built_in = physical_type.as<ccc::ast::BuiltIn>();
@@ -191,6 +236,7 @@ bool SymbolTreeNode::writeValueFromVariant(QVariant value, const ccc::ast::Node&
return false;
}
}
break;
}
case ccc::ast::ENUM:
@@ -266,11 +312,14 @@ QString SymbolTreeNode::generateDisplayString(
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)
if (array.element_count > 0 &&
element_type.name == "char" &&
location.type == SymbolTreeLocation::MEMORY)
{
char* string = cpu.stringFromPointer(location.address);
if (string)
return QString("\"%1\"").arg(string);
std::optional<std::string> string = cpu.ReadString(
location.address, 256, MemoryInterface::ALLOW_LONG_STRINGS);
if (string.has_value())
return QString("\"%1\"").arg(*string);
}
QString result;
@@ -412,9 +461,10 @@ QString SymbolTreeNode::generateDisplayString(
if (pointer_or_reference.is_pointer && value_type.name == "char")
{
const char* string = cpu.stringFromPointer(address);
if (string)
result += QString(" \"%1\"").arg(string);
std::optional<std::string> string = cpu.ReadString(
address, 256, MemoryInterface::ALLOW_LONG_STRINGS);
if (string.has_value())
result += QString(" \"%1\"").arg(*string);
}
else if (depth == 0)
{
+11 -2
View File
@@ -62,11 +62,20 @@ public:
const ccc::SymbolDatabase& database,
const SymbolTreeDisplayOptions& display_options);
QVariant readValueAsVariant(const ccc::ast::Node& physical_type, DebugInterface& cpu, const ccc::SymbolDatabase& database) const;
bool writeValueFromVariant(QVariant value, const ccc::ast::Node& physical_type, DebugInterface& cpu) const;
QVariant readValueAsVariant(
const ccc::ast::Node& physical_type,
DebugInterface& cpu,
const ccc::SymbolDatabase& database) const;
bool writeValueFromVariant(
QVariant value,
const ccc::ast::Node& physical_type,
DebugInterface& cpu,
const ccc::SymbolDatabase& database) const;
bool updateDisplayString(
DebugInterface& cpu, const ccc::SymbolDatabase& database, const SymbolTreeDisplayOptions& display);
QString generateDisplayString(
const ccc::ast::Node& physical_type,
DebugInterface& cpu,
+6 -37
View File
@@ -63,37 +63,6 @@ void DebugInterface::resumeCpu()
VMManager::SetPaused(false);
}
char* DebugInterface::stringFromPointer(u32 p)
{
const int BUFFER_LEN = 64;
static char buf[BUFFER_LEN] = {0};
if (!isValidAddress(p))
return NULL;
// This is going to blow up if it hits a TLB miss..
// Hopefully the checks in isValidAddress() are sufficient.
for (u32 i = 0; i < BUFFER_LEN; i++)
{
char c = Read8(p + i);
buf[i] = c;
if (c == 0)
{
return i > 0 ? buf : NULL;
}
else if (c < 0x20 || c >= 0x7f)
{
// non printable character
return NULL;
}
}
buf[BUFFER_LEN - 1] = 0;
buf[BUFFER_LEN - 2] = '~';
return buf;
}
std::optional<u32> DebugInterface::getCallerStackPointer(const ccc::Function& currentFunction)
{
u32 sp = getRegister(EECAT_GPR, 29);
@@ -336,12 +305,12 @@ bool R5900DebugInterface::Write128(u32 address, u128 value)
return isValidAddress(address) && vtlb_memSafeWriteBytes(address, &value, sizeof(value));
}
bool R5900DebugInterface::WriteBytes(u32 address, void* src, u32 size)
bool R5900DebugInterface::WriteBytes(u32 address, const void* src, u32 size)
{
return vtlb_memSafeWriteBytes(address, src, size);
}
bool R5900DebugInterface::CompareBytes(u32 address, void* src, u32 size)
bool R5900DebugInterface::CompareBytes(u32 address, const void* src, u32 size)
{
return vtlb_memSafeCmpBytes(address, src, size) == 0;
}
@@ -839,12 +808,12 @@ bool R3000DebugInterface::Write128(u32 address, u128 value)
return true;
}
bool R3000DebugInterface::WriteBytes(u32 address, void* src, u32 size)
bool R3000DebugInterface::WriteBytes(u32 address, const void* src, u32 size)
{
return iopMemSafeWriteBytes(address, src, size);
}
bool R3000DebugInterface::CompareBytes(u32 address, void* src, u32 size)
bool R3000DebugInterface::CompareBytes(u32 address, const void* src, u32 size)
{
return iopMemSafeCmpBytes(address, src, size) == 0;
}
@@ -1185,12 +1154,12 @@ bool ElfMemoryReader::Write128(u32 address, u128 value)
return false;
}
bool ElfMemoryReader::WriteBytes(u32 address, void* src, u32 size)
bool ElfMemoryReader::WriteBytes(u32 address, const void* src, u32 size)
{
return false;
}
bool ElfMemoryReader::CompareBytes(u32 address, void* src, u32 size)
bool ElfMemoryReader::CompareBytes(u32 address, const void* src, u32 size)
{
std::optional<std::span<const u8>> bytes = m_elf.get_virtual(address, size);
if (!bytes.has_value())
+6 -7
View File
@@ -80,7 +80,6 @@ public:
bool isCpuPaused();
void pauseCpu();
void resumeCpu();
char* stringFromPointer(u32 p);
std::optional<u32> getCallerStackPointer(const ccc::Function& currentFunction);
std::optional<u32> getStackFrameSize(const ccc::Function& currentFunction);
@@ -115,9 +114,9 @@ public:
bool Write32(u32 address, u32 value) override;
bool Write64(u32 address, u64 value) override;
bool Write128(u32 address, u128 value) override;
bool WriteBytes(u32 address, void* src, u32 size) override;
bool WriteBytes(u32 address, const void* src, u32 size) override;
bool CompareBytes(u32 address, void* src, u32 size) override;
bool CompareBytes(u32 address, const void* src, u32 size) override;
// register stuff
int getRegisterCategoryCount() override;
@@ -161,9 +160,9 @@ public:
bool Write32(u32 address, u32 value) override;
bool Write64(u32 address, u64 value) override;
bool Write128(u32 address, u128 value) override;
bool WriteBytes(u32 address, void* src, u32 size) override;
bool WriteBytes(u32 address, const void* src, u32 size) override;
bool CompareBytes(u32 address, void* src, u32 size) override;
bool CompareBytes(u32 address, const void* src, u32 size) override;
// register stuff
int getRegisterCategoryCount() override;
@@ -210,9 +209,9 @@ public:
bool Write32(u32 address, u32 value) override;
bool Write64(u32 address, u64 value) override;
bool Write128(u32 address, u128 value) override;
bool WriteBytes(u32 address, void* src, u32 size) override;
bool WriteBytes(u32 address, const void* src, u32 size) override;
bool CompareBytes(u32 address, void* src, u32 size) override;
bool CompareBytes(u32 address, const void* src, u32 size) override;
protected:
const ccc::ElfFile& m_elf;
+2 -2
View File
@@ -607,12 +607,12 @@ bool IOPMemoryInterface::Write128(u32 address, u128 value)
return false;
}
bool IOPMemoryInterface::WriteBytes(u32 address, void* src, u32 size)
bool IOPMemoryInterface::WriteBytes(u32 address, const void* src, u32 size)
{
return iopMemSafeWriteBytes(address, src, size);
}
bool IOPMemoryInterface::CompareBytes(u32 address, void* src, u32 size)
bool IOPMemoryInterface::CompareBytes(u32 address, const void* src, u32 size)
{
return iopMemSafeCmpBytes(address, src, size) == 0;
}
+2 -2
View File
@@ -136,7 +136,7 @@ public:
bool Write32(u32 address, u32 value) override;
bool Write64(u32 address, u64 value) override;
bool Write128(u32 address, u128 value) override;
bool WriteBytes(u32 address, void* src, u32 size) override;
bool WriteBytes(u32 address, const void* src, u32 size) override;
bool CompareBytes(u32 address, void* src, u32 size) override;
bool CompareBytes(u32 address, const void* src, u32 size) override;
};
+2 -2
View File
@@ -1257,12 +1257,12 @@ bool EEMemoryInterface::Write128(u32 address, u128 value)
return true;
}
bool EEMemoryInterface::WriteBytes(u32 address, void* src, u32 size)
bool EEMemoryInterface::WriteBytes(u32 address, const void* src, u32 size)
{
return vtlb_memSafeWriteBytes(address, src, size);
}
bool EEMemoryInterface::CompareBytes(u32 address, void* src, u32 size)
bool EEMemoryInterface::CompareBytes(u32 address, const void* src, u32 size)
{
return vtlb_memSafeCmpBytes(address, src, size) == 0;
}
+2 -2
View File
@@ -206,7 +206,7 @@ public:
bool Write32(u32 address, u32 value) override;
bool Write64(u32 address, u64 value) override;
bool Write128(u32 address, u128 value) override;
bool WriteBytes(u32 address, void* src, u32 size) override;
bool WriteBytes(u32 address, const void* src, u32 size) override;
bool CompareBytes(u32 address, void* src, u32 size) override;
bool CompareBytes(u32 address, const void* src, u32 size) override;
};
+2 -2
View File
@@ -21,9 +21,9 @@ public:
MOCK_METHOD(bool, Write32, (u32 address, u32 value), (override));
MOCK_METHOD(bool, Write64, (u32 address, u64 value), (override));
MOCK_METHOD(bool, Write128, (u32 address, u128 value), (override));
MOCK_METHOD(bool, WriteBytes, (u32 address, void* src, u32 size), (override));
MOCK_METHOD(bool, WriteBytes, (u32 address, const void* src, u32 size), (override));
MOCK_METHOD(bool, CompareBytes, (u32 address, void* src, u32 size), (override));
MOCK_METHOD(bool, CompareBytes, (u32 address, const void* src, u32 size), (override));
struct SetValidOutParameterAction
{