From 040d9a43367f4e2a3aa57ddb47c41d5d13b5b39e Mon Sep 17 00:00:00 2001 From: TryTwo Date: Mon, 19 May 2025 23:13:00 -0700 Subject: [PATCH 1/3] Debugger symbols: Add new symbol type: Notes.. Notes are for naming single instructions, or small groups of instructions. Notes are separate from function symbols, and can be searched separately. Unlike functions, notes of different length can overlap each other. In the instruction window, a note will always display over the function symbol. --- Source/Core/Common/SymbolDB.cpp | 1 + Source/Core/Common/SymbolDB.h | 13 +++ Source/Core/Core/PowerPC/PPCSymbolDB.cpp | 89 ++++++++++++++++++- Source/Core/Core/PowerPC/PPCSymbolDB.h | 5 +- .../DolphinQt/Debugger/CodeViewWidget.cpp | 19 +++- 5 files changed, 122 insertions(+), 5 deletions(-) diff --git a/Source/Core/Common/SymbolDB.cpp b/Source/Core/Common/SymbolDB.cpp index f9d352b415..bfdd9f57db 100644 --- a/Source/Core/Common/SymbolDB.cpp +++ b/Source/Core/Common/SymbolDB.cpp @@ -51,6 +51,7 @@ void SymbolDB::Clear(const char* prefix) { // TODO: honor prefix m_functions.clear(); + m_notes.clear(); m_checksum_to_function.clear(); } diff --git a/Source/Core/Common/SymbolDB.h b/Source/Core/Common/SymbolDB.h index 255994b3c2..8930b95996 100644 --- a/Source/Core/Common/SymbolDB.h +++ b/Source/Core/Common/SymbolDB.h @@ -29,6 +29,16 @@ struct SCall u32 call_address; }; +struct Note +{ + std::string name; + u32 address = 0; + u32 size = 0; + int layer = 0; + + Note() = default; +}; + struct Symbol { enum class Type @@ -71,6 +81,7 @@ class SymbolDB { public: using XFuncMap = std::map; + using XNoteMap = std::map; using XFuncPtrMap = std::map>; SymbolDB(); @@ -86,6 +97,7 @@ public: std::vector GetSymbolsFromHash(u32 hash); const XFuncMap& Symbols() const { return m_functions; } + const XNoteMap& Notes() const { return m_notes; } XFuncMap& AccessSymbols() { return m_functions; } bool IsEmpty() const; void Clear(const char* prefix = ""); @@ -94,6 +106,7 @@ public: protected: XFuncMap m_functions; + XNoteMap m_notes; XFuncPtrMap m_checksum_to_function; }; } // namespace Common diff --git a/Source/Core/Core/PowerPC/PPCSymbolDB.cpp b/Source/Core/Core/PowerPC/PPCSymbolDB.cpp index 5351d1ec28..4e98beafef 100644 --- a/Source/Core/Core/PowerPC/PPCSymbolDB.cpp +++ b/Source/Core/Core/PowerPC/PPCSymbolDB.cpp @@ -91,6 +91,46 @@ void PPCSymbolDB::AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAdd } } +void PPCSymbolDB::AddKnownNote(u32 start_addr, u32 size, const std::string& name) +{ + auto iter = m_notes.find(start_addr); + + if (iter != m_notes.end()) + { + // Already got it, just update the name and size. + Common::Note* tempfunc = &iter->second; + tempfunc->name = name; + tempfunc->size = size; + } + else + { + Common::Note tf; + tf.name = name; + tf.address = start_addr; + tf.size = size; + + m_notes[start_addr] = tf; + } +} + +void PPCSymbolDB::DetermineNoteLayers() +{ + if (m_notes.empty()) + return; + + for (auto& note : m_notes) + note.second.layer = 0; + + for (auto iter = m_notes.begin(); iter != m_notes.end(); ++iter) + { + const u32 range = iter->second.address + iter->second.size; + auto search = m_notes.lower_bound(range); + + while (--search != iter) + search->second.layer += 1; + } +} + Common::Symbol* PPCSymbolDB::GetSymbolFromAddr(u32 addr) { auto it = m_functions.lower_bound(addr); @@ -112,6 +152,36 @@ Common::Symbol* PPCSymbolDB::GetSymbolFromAddr(u32 addr) return nullptr; } +Common::Note* PPCSymbolDB::GetNoteFromAddr(u32 addr) +{ + if (m_notes.empty()) + return nullptr; + + auto itn = m_notes.lower_bound(addr); + + // If the address is exactly the start address of a symbol, we're done. + if (itn != m_notes.end() && itn->second.address == addr) + return &itn->second; + + // Otherwise, check whether the address is within the bounds of a symbol. + if (itn == m_notes.begin()) + return nullptr; + + do + { + --itn; + + // If itn's range reaches the address. + if (addr < itn->second.address + itn->second.size) + return &itn->second; + + // If layer is 0, it's the last note that could possibly reach the address, as there are no more + // underlying notes. + } while (itn != m_notes.begin() && itn->second.layer != 0); + + return nullptr; +} + std::string_view PPCSymbolDB::GetDescription(u32 addr) { if (const Common::Symbol* const symbol = GetSymbolFromAddr(addr)) @@ -406,6 +476,7 @@ bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, const std::string& if (strlen(name) > 0) { bool good; + // Notes will be treated the same as Data. const Common::Symbol::Type type = section_name == ".text" || section_name == ".init" ? Common::Symbol::Type::Function : Common::Symbol::Type::Data; @@ -438,7 +509,11 @@ bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, const std::string& if (good) { ++good_count; - AddKnownSymbol(guard, vaddress, size, name_string, object_filename_string, type); + + if (section_name == ".note") + AddKnownNote(vaddress, size, name); + else + AddKnownSymbol(guard, vaddress, size, name_string, object_filename_string, type); } else { @@ -448,6 +523,7 @@ bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, const std::string& } Index(); + DetermineNoteLayers(); NOTICE_LOG_FMT(SYMBOLS, "{} symbols loaded, {} symbols ignored.", good_count, bad_count); return true; } @@ -495,6 +571,17 @@ bool PPCSymbolDB::SaveSymbolMap(const std::string& filename) const file.WriteString(line); } + // Write .note section + auto note_symbols = m_notes | std::views::transform([](auto f) { return f.second; }); + file.WriteString("\n.note section layout\n"); + for (const auto& symbol : note_symbols) + { + // Write symbol address, size, virtual address, alignment, name + const std::string line = fmt::format("{:08x} {:06x} {:08x} {} {}\n", symbol.address, + symbol.size, symbol.address, 0, symbol.name); + file.WriteString(line); + } + return true; } diff --git a/Source/Core/Core/PowerPC/PPCSymbolDB.h b/Source/Core/Core/PowerPC/PPCSymbolDB.h index de3e210327..c9fae570d8 100644 --- a/Source/Core/Core/PowerPC/PPCSymbolDB.h +++ b/Source/Core/Core/PowerPC/PPCSymbolDB.h @@ -25,13 +25,16 @@ public: void AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAddr, u32 size, const std::string& name, const std::string& object_name, Common::Symbol::Type type = Common::Symbol::Type::Function); + void AddKnownNote(u32 start_addr, u32 size, const std::string& name); Common::Symbol* GetSymbolFromAddr(u32 addr) override; + bool NoteExists() const { return !m_notes.empty(); } + Common::Note* GetNoteFromAddr(u32 addr); + void DetermineNoteLayers(); std::string_view GetDescription(u32 addr); void FillInCallers(); - bool LoadMap(const Core::CPUThreadGuard& guard, const std::string& filename, bool bad = false); bool SaveSymbolMap(const std::string& filename) const; bool SaveCodeMap(const Core::CPUThreadGuard& guard, const std::string& filename) const; diff --git a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp index 1f5de76ff0..fd9155a680 100644 --- a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp @@ -137,8 +137,9 @@ constexpr int CODE_VIEW_COLUMN_ADDRESS = 1; constexpr int CODE_VIEW_COLUMN_INSTRUCTION = 2; constexpr int CODE_VIEW_COLUMN_PARAMETERS = 3; constexpr int CODE_VIEW_COLUMN_DESCRIPTION = 4; -constexpr int CODE_VIEW_COLUMN_BRANCH_ARROWS = 5; -constexpr int CODE_VIEW_COLUMNCOUNT = 6; +constexpr int CODE_VIEW_COLUMN_NOTE = 5; +constexpr int CODE_VIEW_COLUMN_BRANCH_ARROWS = 6; +constexpr int CODE_VIEW_COLUMNCOUNT = 7; CodeViewWidget::CodeViewWidget() : m_system(Core::System::GetInstance()), m_ppc_symbol_db(m_system.GetPPCSymbolDB()) @@ -161,6 +162,7 @@ CodeViewWidget::CodeViewWidget() setHorizontalHeaderItem(CODE_VIEW_COLUMN_INSTRUCTION, new QTableWidgetItem(tr("Instr."))); setHorizontalHeaderItem(CODE_VIEW_COLUMN_PARAMETERS, new QTableWidgetItem(tr("Parameters"))); setHorizontalHeaderItem(CODE_VIEW_COLUMN_DESCRIPTION, new QTableWidgetItem(tr("Symbols"))); + setHorizontalHeaderItem(CODE_VIEW_COLUMN_NOTE, new QTableWidgetItem(tr("Notes"))); setHorizontalHeaderItem(CODE_VIEW_COLUMN_BRANCH_ARROWS, new QTableWidgetItem(tr("Branches"))); setFont(Settings::Instance().GetDebugFont()); @@ -333,6 +335,11 @@ void CodeViewWidget::Update(const Core::CPUThreadGuard* guard) std::string param = (split == std::string::npos ? "" : disas.substr(split + 1)); const std::string_view desc = debug_interface.GetDescription(addr); + const Common::Note* note = m_ppc_symbol_db.GetNoteFromAddr(addr); + std::string note_string; + if (note != nullptr) + note_string = note->name; + // Adds whitespace and a minimum size to ins and param. Helps to prevent frequent resizing while // scrolling. const QString ins_formatted = @@ -344,9 +351,11 @@ void CodeViewWidget::Update(const Core::CPUThreadGuard* guard) auto* ins_item = new QTableWidgetItem(ins_formatted); auto* param_item = new QTableWidgetItem(param_formatted); auto* description_item = new QTableWidgetItem(desc_formatted); + auto* note_item = new QTableWidgetItem(QString::fromStdString(note_string)); auto* branch_item = new QTableWidgetItem(); - for (auto* item : {bp_item, addr_item, ins_item, param_item, description_item, branch_item}) + for (auto* item : + {bp_item, addr_item, ins_item, param_item, description_item, note_item, branch_item}) { item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); item->setData(Qt::UserRole, addr); @@ -408,6 +417,7 @@ void CodeViewWidget::Update(const Core::CPUThreadGuard* guard) setItem(i, CODE_VIEW_COLUMN_INSTRUCTION, ins_item); setItem(i, CODE_VIEW_COLUMN_PARAMETERS, param_item); setItem(i, CODE_VIEW_COLUMN_DESCRIPTION, description_item); + setItem(i, CODE_VIEW_COLUMN_NOTE, note_item); setItem(i, CODE_VIEW_COLUMN_BRANCH_ARROWS, branch_item); if (addr == GetAddress()) @@ -416,6 +426,9 @@ void CodeViewWidget::Update(const Core::CPUThreadGuard* guard) } } + m_ppc_symbol_db.NoteExists() ? showColumn(CODE_VIEW_COLUMN_NOTE) : + hideColumn(CODE_VIEW_COLUMN_NOTE); + CalculateBranchIndentation(); m_ppc_symbol_db.FillInCallers(); From 78065359bbcd3196273956ac9c014ec5ae200a53 Mon Sep 17 00:00:00 2001 From: TryTwo Date: Mon, 19 May 2025 23:30:20 -0700 Subject: [PATCH 2/3] Debugger CodeWidget : Add search box for notes. --- Source/Core/DolphinQt/Debugger/CodeWidget.cpp | 49 +++++++++++++++++-- Source/Core/DolphinQt/Debugger/CodeWidget.h | 3 ++ 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/Source/Core/DolphinQt/Debugger/CodeWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeWidget.cpp index e44477aa88..f650f5496f 100644 --- a/Source/Core/DolphinQt/Debugger/CodeWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/CodeWidget.cpp @@ -16,7 +16,9 @@ #include #include #include +#include #include +#include #include #include "Common/Event.h" @@ -120,7 +122,7 @@ void CodeWidget::CreateWidgets() m_box_splitter = new QSplitter(Qt::Vertical); m_box_splitter->setStyleSheet(BOX_SPLITTER_STYLESHEET); - auto add_search_line_edit = [this](const QString& name, QListWidget* list_widget) { + auto add_search_line_edit = [this](const QString& name, QWidget* list_widget) { auto* widget = new QWidget; auto* line_layout = new QGridLayout; auto* label = new QLabel(name); @@ -139,8 +141,12 @@ void CodeWidget::CreateWidgets() m_search_callstack = add_search_line_edit(tr("Callstack"), m_callstack_list); // Symbols + auto* symbols_tab = new QTabWidget; m_symbols_list = new QListWidget; - m_search_symbols = add_search_line_edit(tr("Symbols"), m_symbols_list); + m_note_list = new QListWidget; + symbols_tab->addTab(m_symbols_list, tr("Symbols")); + symbols_tab->addTab(m_note_list, tr("Notes")); + m_search_symbols = add_search_line_edit(tr("Symbols"), symbols_tab); // Function calls m_function_calls_list = new QListWidget; @@ -198,7 +204,7 @@ void CodeWidget::ConnectWidgets() connect(m_search_callstack, &QLineEdit::textChanged, this, &CodeWidget::UpdateCallstack); connect(m_branch_watch, &QPushButton::clicked, this, &CodeWidget::OnBranchWatchDialog); - + connect(m_note_list, &QListWidget::itemPressed, this, &CodeWidget::OnSelectNote); connect(m_symbols_list, &QListWidget::itemPressed, this, &CodeWidget::OnSelectSymbol); connect(m_callstack_list, &QListWidget::itemPressed, this, &CodeWidget::OnSelectCallstack); connect(m_function_calls_list, &QListWidget::itemPressed, this, @@ -236,6 +242,7 @@ void CodeWidget::OnSetCodeAddress(u32 address) void CodeWidget::OnPPCSymbolsChanged() { UpdateSymbols(); + UpdateNotes(); UpdateCallstack(); if (const Common::Symbol* symbol = m_ppc_symbol_db.GetSymbolFromAddr(m_code_view->GetAddress())) { @@ -279,6 +286,7 @@ void CodeWidget::OnSearchSymbols() { m_symbol_filter = m_search_symbols->text(); UpdateSymbols(); + UpdateNotes(); } void CodeWidget::OnSelectSymbol() @@ -298,6 +306,17 @@ void CodeWidget::OnSelectSymbol() m_code_view->setFocus(); } +void CodeWidget::OnSelectNote() +{ + const auto items = m_note_list->selectedItems(); + if (items.isEmpty()) + return; + + const u32 address = items[0]->data(Qt::UserRole).toUInt(); + + m_code_view->SetAddress(address, CodeViewWidget::SetAddressUpdate::WithUpdate); +} + void CodeWidget::OnSelectCallstack() { const auto items = m_callstack_list->selectedItems(); @@ -426,6 +445,30 @@ void CodeWidget::UpdateSymbols() m_symbols_list->sortItems(); } +void CodeWidget::UpdateNotes() +{ + const QString selection = m_note_list->selectedItems().isEmpty() ? + QStringLiteral("") : + m_note_list->selectedItems()[0]->text(); + m_note_list->clear(); + + for (const auto& note : m_ppc_symbol_db.Notes()) + { + const QString name = QString::fromStdString(note.second.name); + + auto* item = new QListWidgetItem(name); + if (name == selection) + item->setSelected(true); + + item->setData(Qt::UserRole, note.second.address); + + if (name.toUpper().indexOf(m_symbol_filter.toUpper()) != -1) + m_note_list->addItem(item); + } + + m_note_list->sortItems(); +} + void CodeWidget::UpdateFunctionCalls(const Common::Symbol* symbol) { m_function_calls_list->clear(); diff --git a/Source/Core/DolphinQt/Debugger/CodeWidget.h b/Source/Core/DolphinQt/Debugger/CodeWidget.h index 62f2642222..1b67a80a1c 100644 --- a/Source/Core/DolphinQt/Debugger/CodeWidget.h +++ b/Source/Core/DolphinQt/Debugger/CodeWidget.h @@ -61,11 +61,13 @@ private: void UpdateCallstack(); void UpdateFunctionCalls(const Common::Symbol* symbol); void UpdateFunctionCallers(const Common::Symbol* symbol); + void UpdateNotes(); void OnPPCSymbolsChanged(); void OnSearchAddress(); void OnSearchSymbols(); void OnSelectSymbol(); + void OnSelectNote(); void OnSelectCallstack(); void OnSelectFunctionCallers(); void OnSelectFunctionCalls(); @@ -84,6 +86,7 @@ private: QListWidget* m_callstack_list; QLineEdit* m_search_symbols; QListWidget* m_symbols_list; + QListWidget* m_note_list; QLineEdit* m_search_calls; QListWidget* m_function_calls_list; QLineEdit* m_search_callers; From c9b815526cb8b867203f7319c7e1f100f5df8ca4 Mon Sep 17 00:00:00 2001 From: TryTwo Date: Tue, 20 May 2025 00:24:16 -0700 Subject: [PATCH 3/3] Debugger CodeViewWidget: Add context options for making and managing Notes. Add popup dialog for editing functions and notes. --- Source/Core/Core/PowerPC/PPCSymbolDB.cpp | 10 + Source/Core/Core/PowerPC/PPCSymbolDB.h | 2 + Source/Core/DolphinQt/CMakeLists.txt | 2 + .../DolphinQt/Debugger/CodeViewWidget.cpp | 177 ++++++++++++------ .../Core/DolphinQt/Debugger/CodeViewWidget.h | 8 +- .../DolphinQt/Debugger/EditSymbolDialog.cpp | 153 +++++++++++++++ .../DolphinQt/Debugger/EditSymbolDialog.h | 51 +++++ Source/Core/DolphinQt/DolphinQt.vcxproj | 2 + 8 files changed, 348 insertions(+), 57 deletions(-) create mode 100644 Source/Core/DolphinQt/Debugger/EditSymbolDialog.cpp create mode 100644 Source/Core/DolphinQt/Debugger/EditSymbolDialog.h diff --git a/Source/Core/Core/PowerPC/PPCSymbolDB.cpp b/Source/Core/Core/PowerPC/PPCSymbolDB.cpp index 4e98beafef..c837f1c041 100644 --- a/Source/Core/Core/PowerPC/PPCSymbolDB.cpp +++ b/Source/Core/Core/PowerPC/PPCSymbolDB.cpp @@ -182,6 +182,16 @@ Common::Note* PPCSymbolDB::GetNoteFromAddr(u32 addr) return nullptr; } +void PPCSymbolDB::DeleteFunction(u32 start_address) +{ + m_functions.erase(start_address); +} + +void PPCSymbolDB::DeleteNote(u32 start_address) +{ + m_notes.erase(start_address); +} + std::string_view PPCSymbolDB::GetDescription(u32 addr) { if (const Common::Symbol* const symbol = GetSymbolFromAddr(addr)) diff --git a/Source/Core/Core/PowerPC/PPCSymbolDB.h b/Source/Core/Core/PowerPC/PPCSymbolDB.h index c9fae570d8..b96ad2f42f 100644 --- a/Source/Core/Core/PowerPC/PPCSymbolDB.h +++ b/Source/Core/Core/PowerPC/PPCSymbolDB.h @@ -31,6 +31,8 @@ public: bool NoteExists() const { return !m_notes.empty(); } Common::Note* GetNoteFromAddr(u32 addr); void DetermineNoteLayers(); + void DeleteFunction(u32 start_address); + void DeleteNote(u32 start_address); std::string_view GetDescription(u32 addr); diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index a86b41cd68..f42b84925d 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -221,6 +221,8 @@ add_executable(dolphin-emu Debugger/CodeViewWidget.h Debugger/CodeWidget.cpp Debugger/CodeWidget.h + Debugger/EditSymbolDialog.cpp + Debugger/EditSymbolDialog.h Debugger/GekkoSyntaxHighlight.cpp Debugger/GekkoSyntaxHighlight.h Debugger/JitBlockTableModel.cpp diff --git a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp index fd9155a680..8bc40150e1 100644 --- a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp @@ -37,6 +37,7 @@ #include "Core/PowerPC/PowerPC.h" #include "Core/System.h" #include "DolphinQt/Debugger/AssembleInstructionDialog.h" +#include "DolphinQt/Debugger/EditSymbolDialog.h" #include "DolphinQt/Debugger/PatchInstructionDialog.h" #include "DolphinQt/Host.h" #include "DolphinQt/QtUtils/FromStdString.h" @@ -579,8 +580,6 @@ void CodeViewWidget::OnContextMenu() const u32 addr = GetContextAddress(); - const bool has_symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr); - auto* follow_branch_action = menu->addAction(tr("Follow &Branch"), this, &CodeViewWidget::OnFollowBranch); @@ -600,17 +599,17 @@ void CodeViewWidget::OnContextMenu() menu->addAction(tr("Copy Tar&get Address"), this, &CodeViewWidget::OnCopyTargetAddress); menu->addSeparator(); - auto* symbol_rename_action = - menu->addAction(tr("&Rename Symbol"), this, &CodeViewWidget::OnRenameSymbol); - auto* symbol_size_action = - menu->addAction(tr("Set Symbol &Size"), this, &CodeViewWidget::OnSetSymbolSize); - auto* symbol_end_action = - menu->addAction(tr("Set Symbol &End Address"), this, &CodeViewWidget::OnSetSymbolEndAddress); + auto* symbol_add_action = + menu->addAction(tr("&Add function symbol"), this, &CodeViewWidget::OnAddFunction); + auto* symbol_edit_action = + menu->addAction(tr("&Edit function symbol"), this, &CodeViewWidget::OnEditSymbol); + + auto* note_add_action = menu->addAction(tr("Add Note"), this, &CodeViewWidget::OnAddNote); + auto* note_edit_action = menu->addAction(tr("Edit Note"), this, &CodeViewWidget::OnEditNote); + menu->addSeparator(); auto* run_to_action = menu->addAction(tr("Run &to Here"), this, &CodeViewWidget::OnRunToHere); - auto* function_action = - menu->addAction(tr("&Add Function"), this, &CodeViewWidget::OnAddFunction); auto* ppc_action = menu->addAction(tr("PPC vs Host"), this, &CodeViewWidget::OnPPCComparison); auto* insert_blr_action = menu->addAction(tr("&Insert BLR"), this, &CodeViewWidget::OnInsertBLR); auto* insert_nop_action = menu->addAction(tr("Insert &NOP"), this, &CodeViewWidget::OnInsertNOP); @@ -659,20 +658,24 @@ void CodeViewWidget::OnContextMenu() follow_branch_action->setEnabled(follow_branch_enabled); for (auto* action : - {copy_address_action, copy_line_action, copy_hex_action, function_action, run_to_action, - ppc_action, insert_blr_action, insert_nop_action, replace_action, assemble_action}) + {copy_address_action, copy_line_action, copy_hex_action, symbol_add_action, + symbol_edit_action, note_add_action, note_edit_action, run_to_action, ppc_action, + insert_blr_action, insert_nop_action, replace_action, assemble_action}) { action->setEnabled(running); } - for (auto* action : {symbol_rename_action, symbol_size_action, symbol_end_action}) - action->setEnabled(has_symbol); - for (auto* action : {copy_target_memory, show_target_memory}) { action->setEnabled(valid_load_store); } + auto* note = m_ppc_symbol_db.GetNoteFromAddr(addr); + note_edit_action->setEnabled(note != nullptr); + // A note cannot be added ontop of the starting address of another note. + if (note != nullptr && note->address == addr) + note_add_action->setEnabled(false); + restore_action->setEnabled(running && m_system.GetPowerPC().GetDebugInterface().HasEnabledPatch(addr)); @@ -896,6 +899,13 @@ void CodeViewWidget::OnPPCComparison() void CodeViewWidget::OnAddFunction() { const u32 addr = GetContextAddress(); + const int confirm = + QMessageBox::warning(this, tr("Add Function Symbol"), + tr("Force new function symbol to be made at %1?").arg(addr, 0, 16), + QMessageBox::Ok | QMessageBox::Cancel); + + if (confirm != QMessageBox::Ok) + return; Core::CPUThreadGuard guard(m_system); @@ -932,25 +942,80 @@ void CodeViewWidget::OnFollowBranch() SetAddress(branch_addr, SetAddressUpdate::WithDetailedUpdate); } -void CodeViewWidget::OnRenameSymbol() +void CodeViewWidget::OnEditSymbol() { const u32 addr = GetContextAddress(); - Common::Symbol* const symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr); - if (!symbol) + if (symbol == nullptr) + { + OnAddFunction(); + return; + } + + std::string name = symbol->name; + u32 size = symbol->size; + const u32 symbol_address = symbol->address; + + EditSymbolDialog dialog(this, symbol_address, &size, &name); + + if (dialog.exec() != QDialog::Accepted) return; - bool good; - const QString name = - QInputDialog::getText(this, tr("Rename Symbol"), tr("Symbol Name:"), QLineEdit::Normal, - QString::fromStdString(symbol->name), &good, Qt::WindowCloseButtonHint); - - if (good && !name.isEmpty()) + if (dialog.DeleteRequested()) { - symbol->Rename(name.toStdString()); - emit Host::GetInstance()->PPCSymbolsChanged(); + OnDeleteSymbol(); + return; } + + if (symbol->name != name) + symbol->Rename(name); + + if (symbol->size != size) + { + Core::CPUThreadGuard guard(m_system); + PPCAnalyst::ReanalyzeFunction(guard, symbol->address, *symbol, size); + } + + emit Host::GetInstance()->PPCSymbolsChanged(); +} + +void CodeViewWidget::OnDeleteSymbol() +{ + const u32 addr = GetContextAddress(); + Common::Symbol* const symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr); + + if (symbol == nullptr) + return; + + const int confirm = QMessageBox::warning(this, tr("Delete Function Symbol"), + tr("Delete function symbol: %1\nat %2?") + .arg(QString::fromStdString(symbol->name)) + .arg(addr, 0, 16), + QMessageBox::Ok | QMessageBox::Cancel); + + if (confirm != QMessageBox::Ok) + return; + + m_ppc_symbol_db.DeleteFunction(symbol->address); + + emit Host::GetInstance()->PPCSymbolsChanged(); +} + +void CodeViewWidget::OnAddNote() +{ + const u32 note_address = GetContextAddress(); + std::string name = ""; + u32 size = 4; + + EditSymbolDialog dialog(this, note_address, &size, &name, EditSymbolDialog::Type::Note); + + if (dialog.exec() != QDialog::Accepted || dialog.DeleteRequested()) + return; + + m_ppc_symbol_db.AddKnownNote(note_address, size, name); + m_ppc_symbol_db.DetermineNoteLayers(); + emit Host::GetInstance()->PPCSymbolsChanged(); } void CodeViewWidget::OnSelectionChanged() @@ -966,53 +1031,57 @@ void CodeViewWidget::OnSelectionChanged() } } -void CodeViewWidget::OnSetSymbolSize() +void CodeViewWidget::OnEditNote() { - const u32 addr = GetContextAddress(); + const u32 context_address = GetContextAddress(); + Common::Note* const note = m_ppc_symbol_db.GetNoteFromAddr(context_address); - Common::Symbol* const symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr); - - if (!symbol) + if (note == nullptr) return; - bool good; - const int size = QInputDialog::getInt( - this, tr("Rename Symbol"), tr("Symbol Size (%1):").arg(QString::fromStdString(symbol->name)), - symbol->size, 1, 0xFFFF, 1, &good, Qt::WindowCloseButtonHint); + std::string name = note->name; + u32 size = note->size; + const u32 note_address = note->address; - if (!good) + EditSymbolDialog dialog(this, note_address, &size, &name, EditSymbolDialog::Type::Note); + + if (dialog.exec() != QDialog::Accepted) return; - Core::CPUThreadGuard guard(m_system); + if (dialog.DeleteRequested()) + { + OnDeleteNote(); + return; + } + + if (note->name != name || note->size != size) + { + m_ppc_symbol_db.AddKnownNote(note_address, size, name); + m_ppc_symbol_db.DetermineNoteLayers(); + } - PPCAnalyst::ReanalyzeFunction(guard, symbol->address, *symbol, size); emit Host::GetInstance()->PPCSymbolsChanged(); } -void CodeViewWidget::OnSetSymbolEndAddress() +void CodeViewWidget::OnDeleteNote() { - const u32 addr = GetContextAddress(); + const u32 context_address = GetContextAddress(); + Common::Note* const note = m_ppc_symbol_db.GetNoteFromAddr(context_address); - Common::Symbol* const symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr); - - if (!symbol) + if (note == nullptr) return; - bool good; - const QString name = QInputDialog::getText( - this, tr("Set Symbol End Address"), - tr("Symbol End Address (%1):").arg(QString::fromStdString(symbol->name)), QLineEdit::Normal, - QStringLiteral("%1").arg(addr + symbol->size, 8, 16, QLatin1Char('0')), &good, - Qt::WindowCloseButtonHint); + const int confirm = QMessageBox::warning(this, tr("Delete Note"), + tr("Delete Note: %1\nat %2?") + .arg(QString::fromStdString(note->name)) + .arg(context_address, 0, 16), + QMessageBox::Ok | QMessageBox::Cancel); - const u32 address = name.toUInt(&good, 16); - - if (!good) + if (confirm != QMessageBox::Ok) return; - Core::CPUThreadGuard guard(m_system); + m_ppc_symbol_db.DeleteNote(note->address); - PPCAnalyst::ReanalyzeFunction(guard, symbol->address, *symbol, address - symbol->address); emit Host::GetInstance()->PPCSymbolsChanged(); } diff --git a/Source/Core/DolphinQt/Debugger/CodeViewWidget.h b/Source/Core/DolphinQt/Debugger/CodeViewWidget.h index b59bd27277..6760c082eb 100644 --- a/Source/Core/DolphinQt/Debugger/CodeViewWidget.h +++ b/Source/Core/DolphinQt/Debugger/CodeViewWidget.h @@ -87,13 +87,15 @@ private: void OnCopyFunction(); void OnCopyCode(); void OnCopyHex(); - void OnRenameSymbol(); void OnSelectionChanged(); - void OnSetSymbolSize(); - void OnSetSymbolEndAddress(); void OnRunToHere(); void OnAddFunction(); + void OnEditSymbol(); + void OnDeleteSymbol(); + void OnAddNote(); void OnPPCComparison(); + void OnEditNote(); + void OnDeleteNote(); void OnInsertBLR(); void OnInsertNOP(); void OnReplaceInstruction(); diff --git a/Source/Core/DolphinQt/Debugger/EditSymbolDialog.cpp b/Source/Core/DolphinQt/Debugger/EditSymbolDialog.cpp new file mode 100644 index 0000000000..b0ecb32797 --- /dev/null +++ b/Source/Core/DolphinQt/Debugger/EditSymbolDialog.cpp @@ -0,0 +1,153 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DolphinQt/Debugger/EditSymbolDialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +EditSymbolDialog::EditSymbolDialog(QWidget* parent, const u32 symbol_address, u32* symbol_size, + std::string* symbol_name, Type type) + : QDialog(parent), m_symbol_name(symbol_name), m_symbol_size(symbol_size), + m_symbol_address(symbol_address) +{ + m_type = type == Type::Symbol ? tr("Symbol") : tr("Note"); + setWindowTitle(tr("Edit %1").arg(m_type)); + CreateWidgets(); + ConnectWidgets(); +} + +void EditSymbolDialog::CreateWidgets() +{ + m_buttons = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); + m_buttons->addButton(tr("Reset"), QDialogButtonBox::ResetRole); + m_buttons->addButton(tr("Delete"), QDialogButtonBox::DestructiveRole); + + QLabel* info_label = new QLabel(tr("Editing %1 starting at: %2\nWarning: Must save the symbol " + "map for changes to be kept.") + .arg(m_type) + .arg(QString::number(m_symbol_address, 16))); + m_name_edit = new QLineEdit(); + m_name_edit->setPlaceholderText(tr("%1 name").arg(m_type)); + + auto* size_layout = new QHBoxLayout; + QLabel* address_end_label = new QLabel(tr("End Address")); + QLabel* size_lines_label = new QLabel(tr("Lines")); + QLabel* size_hex_label = new QLabel(tr("Size: 0x")); + m_address_end_edit = new QLineEdit(); + m_size_lines_spin = new QSpinBox(); + m_size_hex_edit = new QLineEdit(); + + size_hex_label->setAlignment(Qt::AlignCenter | Qt::AlignRight); + size_lines_label->setAlignment(Qt::AlignCenter | Qt::AlignRight); + + // Get system font and use to size boxes. + QFont font; + QFontMetrics fm(font); + const int width = fm.horizontalAdvance(QLatin1Char('0')) * 2; + m_address_end_edit->setFixedWidth(width * 6); + m_size_hex_edit->setFixedWidth(width * 5); + m_size_lines_spin->setFixedWidth(width * 5); + m_size_hex_edit->setMaxLength(7); + m_size_lines_spin->setRange(0, 99999); + + // Accept hex input only + QRegularExpression rx(QStringLiteral("[0-9a-fA-F]{0,8}")); + QValidator* validator = new QRegularExpressionValidator(rx, this); + m_address_end_edit->setValidator(validator); + m_size_hex_edit->setValidator(validator); + + size_layout->addWidget(address_end_label); + size_layout->addWidget(m_address_end_edit); + size_layout->addWidget(size_hex_label); + size_layout->addWidget(m_size_hex_edit); + size_layout->addWidget(size_lines_label); + size_layout->addWidget(m_size_lines_spin); + + auto* layout = new QVBoxLayout(); + layout->addWidget(info_label); + layout->addWidget(m_name_edit); + layout->addLayout(size_layout); + layout->addWidget(m_buttons); + + setLayout(layout); + + FillFunctionData(); +} + +void EditSymbolDialog::FillFunctionData() +{ + m_name_edit->setText(QString::fromStdString(*m_symbol_name)); + m_size_lines_spin->setValue(*m_symbol_size / 4); + m_size_hex_edit->setText(QString::number(*m_symbol_size, 16)); + m_address_end_edit->setText( + QStringLiteral("%1").arg(m_symbol_address + *m_symbol_size, 8, 16, QLatin1Char('0'))); +} + +void EditSymbolDialog::UpdateAddressData(u32 size) +{ + // Not sure what the max size should be. Definitely not a full 8, so set to 7. + size = size & 0xFFFFFFF; + + m_size_lines_spin->setValue(size / 4); + m_size_hex_edit->setText(QString::number(size, 16)); + m_address_end_edit->setText( + QStringLiteral("%1").arg(m_symbol_address + size, 8, 16, QLatin1Char('0'))); +} + +void EditSymbolDialog::ConnectWidgets() +{ + connect(m_size_lines_spin, QOverload::of(&QSpinBox::valueChanged), this, + [this](int value) { UpdateAddressData(value * 4); }); + + connect(m_size_hex_edit, &QLineEdit::editingFinished, this, [this] { + bool good; + const u32 size = m_size_hex_edit->text().toUInt(&good, 16); + if (good) + UpdateAddressData(size); + }); + + connect(m_address_end_edit, &QLineEdit::textEdited, this, [this] { + bool good; + const u32 end = m_address_end_edit->text().toUInt(&good, 16); + if (good && end > m_symbol_address) + UpdateAddressData(end - m_symbol_address); + }); + + connect(m_buttons, &QDialogButtonBox::accepted, this, &EditSymbolDialog::Accepted); + connect(m_buttons, &QDialogButtonBox::rejected, this, &EditSymbolDialog::reject); + connect(m_buttons, &QDialogButtonBox::clicked, this, [this](QAbstractButton* btn) { + const auto role = m_buttons->buttonRole(btn); + if (role == QDialogButtonBox::ButtonRole::ResetRole) + { + FillFunctionData(); + } + else if (role == QDialogButtonBox::ButtonRole::DestructiveRole) + { + m_delete_chosen = true; + QDialog::accept(); + } + }); +} + +void EditSymbolDialog::Accepted() +{ + const std::string name = m_name_edit->text().toStdString(); + + if (*m_symbol_name != name) + *m_symbol_name = name; + + bool good; + const u32 size = m_size_hex_edit->text().toUInt(&good, 16); + + if (good && *m_symbol_size != size) + *m_symbol_size = size; + + QDialog::accept(); +} diff --git a/Source/Core/DolphinQt/Debugger/EditSymbolDialog.h b/Source/Core/DolphinQt/Debugger/EditSymbolDialog.h new file mode 100644 index 0000000000..c3e3f034e7 --- /dev/null +++ b/Source/Core/DolphinQt/Debugger/EditSymbolDialog.h @@ -0,0 +1,51 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include + +#include +#include +#include "Common/CommonTypes.h" + +class QLineEdit; +class QDialogButtonBox; +class QSpinBox; + +class EditSymbolDialog : public QDialog +{ + Q_OBJECT + +public: + enum class Type + { + Symbol, + Note, + }; + + explicit EditSymbolDialog(QWidget* parent, const u32 symbol_address, u32* symbol_size, + std::string* symbol_name, Type type = Type::Symbol); + + bool DeleteRequested() const { return m_delete_chosen; } + +private: + void CreateWidgets(); + void ConnectWidgets(); + void FillFunctionData(); + void UpdateAddressData(u32 size); + void Accepted(); + + QLineEdit* m_name_edit; + QSpinBox* m_size_lines_spin; + QLineEdit* m_size_hex_edit; + QLineEdit* m_address_end_edit; + + QDialogButtonBox* m_buttons; + + QString m_type; + std::string* m_symbol_name; + u32* m_symbol_size; + const u32 m_symbol_address; + + bool m_delete_chosen = false; +}; diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index dc67819d75..be9dcc2bda 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -144,6 +144,7 @@ + @@ -364,6 +365,7 @@ +