From 46a0c2d5b6f36948fd6f1e413273ea7ab5e33c8d Mon Sep 17 00:00:00 2001 From: Ty Lamontagne Date: Thu, 26 Oct 2023 18:26:16 -0400 Subject: [PATCH] Debugger: IOP Function Tree Co-authored-by: Ziemas --- pcsx2-qt/Debugger/CpuWidget.cpp | 222 +++++++++++++++---- pcsx2-qt/Debugger/CpuWidget.h | 5 +- pcsx2-qt/Debugger/CpuWidget.ui | 36 ++- pcsx2-qt/Debugger/Models/BreakpointModel.cpp | 4 +- pcsx2/DebugTools/SymbolMap.cpp | 92 +++++++- pcsx2/DebugTools/SymbolMap.h | 40 +++- pcsx2/IopBios.cpp | 111 ++++++++-- pcsx2/IopBios.h | 1 + pcsx2/R3000AInterpreter.cpp | 9 + pcsx2/x86/iR3000A.cpp | 9 + 10 files changed, 468 insertions(+), 61 deletions(-) diff --git a/pcsx2-qt/Debugger/CpuWidget.cpp b/pcsx2-qt/Debugger/CpuWidget.cpp index 920ef09567..492c5d3291 100644 --- a/pcsx2-qt/Debugger/CpuWidget.cpp +++ b/pcsx2-qt/Debugger/CpuWidget.cpp @@ -94,6 +94,8 @@ CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu) connect(m_ui.tabWidgetRegFunc, &QTabWidget::currentChanged, [this](int i) {if(i == 1){updateFunctionList(true);} }); connect(m_ui.listFunctions, &QListWidget::customContextMenuRequested, this, &CpuWidget::onFuncListContextMenu); connect(m_ui.listFunctions, &QListWidget::itemDoubleClicked, this, &CpuWidget::onFuncListDoubleClick); + connect(m_ui.treeModules, &QTreeWidget::customContextMenuRequested, this, &CpuWidget::onModuleTreeContextMenu); + connect(m_ui.treeModules, &QTreeWidget::itemDoubleClicked, this, &CpuWidget::onModuleTreeDoubleClick); connect(m_ui.btnRefreshFunctions, &QPushButton::clicked, [this] { updateFunctionList(); }); connect(m_ui.txtFuncSearch, &QLineEdit::textChanged, [this] { updateFunctionList(); }); @@ -109,6 +111,15 @@ CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu) m_ui.registerWidget->SetCpu(&cpu); m_ui.memoryviewWidget->SetCpu(&cpu); + if (cpu.getCpuType() == BREAKPOINT_EE) + { + m_ui.treeModules->setVisible(false); + } + else + { + m_ui.treeModules->header()->setSectionResizeMode(0, QHeaderView::ResizeMode::ResizeToContents); + m_ui.listFunctions->setVisible(false); + } this->repaint(); } @@ -396,7 +407,7 @@ void CpuWidget::contextBPListPasteCSV() } // Condition - if (fields[4] != "No Condition") + if (!fields[4].isEmpty()) { PostfixExpression expr; bp.hasCond = true; @@ -467,36 +478,82 @@ void CpuWidget::updateFunctionList(bool whenEmpty) if (!m_cpu.isAlive()) return; - if (whenEmpty && m_ui.listFunctions->count()) - return; - - m_ui.listFunctions->clear(); - - const auto demangler = demangler::CDemangler::createGcc(); - const QString filter = m_ui.txtFuncSearch->text().toLower(); - for (const auto& symbol : m_cpu.GetSymbolMap().GetAllSymbols(SymbolType::ST_FUNCTION)) + if (m_cpu.getCpuType() == BREAKPOINT_EE || !m_moduleView) { - QString symbolName = symbol.name.c_str(); - if (m_demangleFunctions) + if (whenEmpty && m_ui.listFunctions->count()) + return; + + m_ui.listFunctions->clear(); + + const auto demangler = demangler::CDemangler::createGcc(); + const QString filter = m_ui.txtFuncSearch->text().toLower(); + for (const auto& symbol : m_cpu.GetSymbolMap().GetAllSymbols(SymbolType::ST_FUNCTION)) { - symbolName = QString(demangler->demangleToString(symbol.name).c_str()); + QString symbolName = symbol.name.c_str(); + if (m_demangleFunctions) + { + symbolName = QString(demangler->demangleToString(symbol.name).c_str()); - // If the name isn't mangled, or it doesn't understand, it'll return an empty string - // Fall back to the original name if this is the case - if (symbolName.isEmpty()) - symbolName = symbol.name.c_str(); + // If the name isn't mangled, or it doesn't understand, it'll return an empty string + // Fall back to the original name if this is the case + if (symbolName.isEmpty()) + symbolName = symbol.name.c_str(); + } + + if (filter.size() && !symbolName.toLower().contains(filter)) + continue; + + QListWidgetItem* item = new QListWidgetItem(); + + item->setText(QString("%0 %1").arg(FilledQStringFromValue(symbol.address, 16)).arg(symbolName)); + + item->setData(256, symbol.address); + + m_ui.listFunctions->addItem(item); } + } + else + { + const auto demangler = demangler::CDemangler::createGcc(); + const QString filter = m_ui.txtFuncSearch->text().toLower(); - if (filter.size() && !symbolName.toLower().contains(filter)) - continue; + m_ui.treeModules->clear(); + for (const auto& module : m_cpu.GetSymbolMap().GetModules()) + { + QTreeWidgetItem* moduleItem = new QTreeWidgetItem(m_ui.treeModules, QStringList({QString(module.name.c_str()), QString("%0.%1").arg(module.version.major).arg(module.version.minor), QString::number(module.exports.size())})); + QList functions; + for (const auto& sym : module.exports) + { + if (!QString(sym.name.c_str()).toLower().contains(filter)) + continue; - QListWidgetItem* item = new QListWidgetItem(); + QString symbolName = QString(sym.name.c_str()); + if (m_demangleFunctions) + { + QString demangledName = QString(demangler->demangleToString(sym.name).c_str()); + if (!demangledName.isEmpty()) + symbolName = demangledName; + } + QTreeWidgetItem* functionItem = new QTreeWidgetItem(moduleItem, QStringList(QString("%0 %1").arg(FilledQStringFromValue(sym.address, 16)).arg(symbolName))); + functionItem->setData(0, 256, sym.address); + functions.append(functionItem); + } + moduleItem->addChildren(functions); - item->setText(QString("%0 %1").arg(FilledQStringFromValue(symbol.address, 16)).arg(symbolName)); - - item->setData(256, symbol.address); - - m_ui.listFunctions->addItem(item); + if (!filter.isEmpty() && functions.size()) + { + moduleItem->setExpanded(true); + m_ui.treeModules->insertTopLevelItem(0, moduleItem); + } + else if (filter.isEmpty()) + { + m_ui.treeModules->insertTopLevelItem(0, moduleItem); + } + else + { + delete moduleItem; + } + } } } @@ -555,19 +612,6 @@ void CpuWidget::onFuncListContextMenu(QPoint pos) else m_funclistContextMenu->clear(); - //: "Demangling" is the opposite of "Name mangling", which is a process where a compiler takes function names and combines them with other characteristics of the function (e.g. what types of data it accepts) to ensure they stay unique even when multiple functions exist with the same name (but different inputs / const-ness). See here: https://en.wikipedia.org/wiki/Name_mangling#C++ - QAction* demangleAction = new QAction(tr("Demangle Symbols"), m_ui.listFunctions); - demangleAction->setCheckable(true); - demangleAction->setChecked(m_demangleFunctions); - - connect(demangleAction, &QAction::triggered, [this] { - m_demangleFunctions = !m_demangleFunctions; - m_ui.disassemblyWidget->setDemangle(m_demangleFunctions); - updateFunctionList(); - }); - - m_funclistContextMenu->addAction(demangleAction); - QAction* copyName = new QAction(tr("Copy Function Name"), m_ui.listFunctions); connect(copyName, &QAction::triggered, [this] { // We only store the address in the widget item @@ -601,9 +645,38 @@ void CpuWidget::onFuncListContextMenu(QPoint pos) m_ui.memoryviewWidget->gotoAddress(m_ui.listFunctions->selectedItems().first()->data(256).toUInt()); }); + m_funclistContextMenu->addSeparator(); + m_funclistContextMenu->addAction(gotoMemory); + //: "Demangling" is the opposite of "Name mangling", which is a process where a compiler takes function names and combines them with other characteristics of the function (e.g. what types of data it accepts) to ensure they stay unique even when multiple functions exist with the same name (but different inputs / const-ness). See here: https://en.wikipedia.org/wiki/Name_mangling#C++ + QAction* demangleAction = new QAction(tr("Demangle Symbols"), m_ui.listFunctions); + demangleAction->setCheckable(true); + demangleAction->setChecked(m_demangleFunctions); + connect(demangleAction, &QAction::triggered, [this] { + m_demangleFunctions = !m_demangleFunctions; + m_ui.disassemblyWidget->setDemangle(m_demangleFunctions); + updateFunctionList(); + }); + + m_funclistContextMenu->addAction(demangleAction); + + if (m_cpu.getCpuType() == BREAKPOINT_IOP) + { + QAction* moduleViewAction = new QAction(tr("Module Tree"), m_ui.listFunctions); + moduleViewAction->setCheckable(true); + moduleViewAction->setChecked(m_moduleView); + + connect(moduleViewAction, &QAction::triggered, [this] { + m_moduleView = !m_moduleView; + m_ui.treeModules->setVisible(m_moduleView); + m_ui.listFunctions->setVisible(!m_moduleView); + updateFunctionList(); + }); + + m_funclistContextMenu->addAction(moduleViewAction); + } m_funclistContextMenu->popup(m_ui.listFunctions->viewport()->mapToGlobal(pos)); } @@ -612,6 +685,81 @@ void CpuWidget::onFuncListDoubleClick(QListWidgetItem* item) m_ui.disassemblyWidget->gotoAddress(item->data(256).toUInt()); } +void CpuWidget::onModuleTreeContextMenu(QPoint pos) +{ + if (!m_moduleTreeContextMenu) + m_moduleTreeContextMenu = new QMenu(m_ui.treeModules); + else + m_moduleTreeContextMenu->clear(); + + if (m_ui.treeModules->selectedItems().count() && m_ui.treeModules->selectedItems().first()->data(0, 256).isValid()) + { + QAction* copyName = new QAction(tr("Copy Function Name"), m_ui.treeModules); + connect(copyName, &QAction::triggered, [this] { + QApplication::clipboard()->setText(m_cpu.GetSymbolMap().GetLabelName(m_ui.treeModules->selectedItems().first()->data(0, 256).toUInt()).c_str()); + }); + m_moduleTreeContextMenu->addAction(copyName); + + QAction* copyAddress = new QAction(tr("Copy Function Address"), m_ui.treeModules); + connect(copyAddress, &QAction::triggered, [this] { + const QString addressString = FilledQStringFromValue(m_ui.treeModules->selectedItems().first()->data(0, 256).toUInt(), 16); + QApplication::clipboard()->setText(addressString); + }); + m_moduleTreeContextMenu->addAction(copyAddress); + + m_moduleTreeContextMenu->addSeparator(); + + QAction* gotoDisasm = new QAction(tr("Go to in Disassembly"), m_ui.treeModules); + connect(gotoDisasm, &QAction::triggered, [this] { + m_ui.disassemblyWidget->gotoAddress(m_ui.treeModules->selectedItems().first()->data(0, 256).toUInt()); + }); + m_moduleTreeContextMenu->addAction(gotoDisasm); + + QAction* gotoMemory = new QAction(tr("Go to in Memory View"), m_ui.treeModules); + connect(gotoMemory, &QAction::triggered, [this] { + m_ui.memoryviewWidget->gotoAddress(m_ui.treeModules->selectedItems().first()->data(0, 256).toUInt()); + }); + m_moduleTreeContextMenu->addAction(gotoMemory); + } + + //: "Demangling" is the opposite of "Name mangling", which is a process where a compiler takes function names and combines them with other characteristics of the function (e.g. what types of data it accepts) to ensure they stay unique even when multiple functions exist with the same name (but different inputs / const-ness). See here: https://en.wikipedia.org/wiki/Name_mangling#C++ + QAction* demangleAction = new QAction(tr("Demangle Symbols"), m_ui.treeModules); + demangleAction->setCheckable(true); + demangleAction->setChecked(m_demangleFunctions); + + connect(demangleAction, &QAction::triggered, [this] { + m_demangleFunctions = !m_demangleFunctions; + m_ui.disassemblyWidget->setDemangle(m_demangleFunctions); + updateFunctionList(); + }); + + m_moduleTreeContextMenu->addSeparator(); + + m_moduleTreeContextMenu->addAction(demangleAction); + + QAction* moduleViewAction = new QAction(tr("Module Tree"), m_ui.treeModules); + moduleViewAction->setCheckable(true); + moduleViewAction->setChecked(m_moduleView); + + connect(moduleViewAction, &QAction::triggered, [this] { + m_moduleView = !m_moduleView; + m_ui.treeModules->setVisible(m_moduleView); + m_ui.listFunctions->setVisible(!m_moduleView); + updateFunctionList(); + }); + + m_moduleTreeContextMenu->addAction(moduleViewAction); + + m_moduleTreeContextMenu->popup(m_ui.treeModules->viewport()->mapToGlobal(pos)); +} + +void CpuWidget::onModuleTreeDoubleClick(QTreeWidgetItem* item) +{ + if (item->data(0, 256).isValid()) + { + m_ui.disassemblyWidget->gotoAddress(item->data(0, 256).toUInt()); + } +} void CpuWidget::updateStackFrames() { m_stackModel.refreshData(); diff --git a/pcsx2-qt/Debugger/CpuWidget.h b/pcsx2-qt/Debugger/CpuWidget.h index 771c97544b..22d54ad6bb 100644 --- a/pcsx2-qt/Debugger/CpuWidget.h +++ b/pcsx2-qt/Debugger/CpuWidget.h @@ -74,7 +74,8 @@ public slots: void onFuncListContextMenu(QPoint pos); void onFuncListDoubleClick(QListWidgetItem* item); bool getDemangleFunctions() const { return m_demangleFunctions; } - + void onModuleTreeContextMenu(QPoint pos); + void onModuleTreeDoubleClick(QTreeWidgetItem* item); void reloadCPUWidgets() { if (!QtHost::IsOnUIThread()) @@ -99,6 +100,7 @@ private: QMenu* m_stacklistContextMenu = 0; QMenu* m_funclistContextMenu = 0; + QMenu* m_moduleTreeContextMenu = 0; Ui::CpuWidget m_ui; @@ -110,4 +112,5 @@ private: StackModel m_stackModel; bool m_demangleFunctions = true; + bool m_moduleView = true; }; diff --git a/pcsx2-qt/Debugger/CpuWidget.ui b/pcsx2-qt/Debugger/CpuWidget.ui index a2b35f1e01..b04725c671 100644 --- a/pcsx2-qt/Debugger/CpuWidget.ui +++ b/pcsx2-qt/Debugger/CpuWidget.ui @@ -115,6 +115,40 @@ 0 + + + + true + + + Qt::CustomContextMenu + + + 8 + + + true + + + 3 + + + + Module + + + + + Version + + + + + Count + + + + @@ -324,7 +358,7 @@ - QTabWidget::South + QTabWidget::North 0 diff --git a/pcsx2-qt/Debugger/Models/BreakpointModel.cpp b/pcsx2-qt/Debugger/Models/BreakpointModel.cpp index 3b97f3e944..374bba1b98 100644 --- a/pcsx2-qt/Debugger/Models/BreakpointModel.cpp +++ b/pcsx2-qt/Debugger/Models/BreakpointModel.cpp @@ -63,7 +63,7 @@ QVariant BreakpointModel::data(const QModelIndex& index, int role) const // Note: Fix up the disassemblymanager so we can use it here, instead of calling a function through the disassemblyview (yuck) return m_cpu.disasm(bp->addr, true).c_str(); case BreakpointColumns::CONDITION: - return bp->hasCond ? QString::fromLocal8Bit(bp->cond.expressionString) : tr("No Condition"); + return bp->hasCond ? QString::fromLocal8Bit(bp->cond.expressionString) : ""; case BreakpointColumns::HITS: return tr("--"); case BreakpointColumns::ENABLED: @@ -235,7 +235,7 @@ QVariant BreakpointModel::headerData(int section, Qt::Orientation orientation, i return tr("HITS"); case BreakpointColumns::ENABLED: //: Warning: limited space available. Abbreviate if needed. - return tr("ENABLED"); + return tr("X"); default: return QVariant(); } diff --git a/pcsx2/DebugTools/SymbolMap.cpp b/pcsx2/DebugTools/SymbolMap.cpp index d231a37373..f81da07a56 100644 --- a/pcsx2/DebugTools/SymbolMap.cpp +++ b/pcsx2/DebugTools/SymbolMap.cpp @@ -40,6 +40,7 @@ void SymbolMap::Clear() functions.clear(); labels.clear(); data.clear(); + modules.clear(); } @@ -222,7 +223,7 @@ std::string SymbolMap::GetDescription(unsigned int address) const return descriptionTemp; } -std::vector SymbolMap::GetAllSymbols(SymbolType symmask) +std::vector SymbolMap::GetAllSymbols(SymbolType symmask) const { std::vector result; @@ -273,6 +274,7 @@ void SymbolMap::AddFunction(const std::string& name, u32 address, u32 size) func.start = address; func.size = size; func.index = (int)functions.size(); + func.name = name; functions[address] = func; functions.insert(std::make_pair(address, func)); @@ -509,3 +511,91 @@ DataType SymbolMap::GetDataType(u32 startAddress) const return DATATYPE_NONE; return it->second.type; } + +bool SymbolMap::AddModule(const std::string& name, ModuleVersion version) +{ + std::lock_guard guard(m_lock); + auto it = modules.find(name); + if (it != modules.end()) + { + for (auto [itr, end] = modules.equal_range(name); itr != end; ++itr) + { + // Different major versions, we treat this one as a different module + if (itr->second.version.major != version.major) + continue; + + // RegisterLibraryEntries will fail if the new minor ver is <= the old minor ver + // and the major version is the same + if (itr->second.version.minor >= version.minor) + return false; + + // Remove the old module and its export table + RemoveModule(name, itr->second.version); + break; + } + } + + modules.insert(std::make_pair(name, ModuleEntry{name, version, {}})); + return true; +} + +void SymbolMap::AddModuleExport(const std::string& module, ModuleVersion version, const std::string& name, u32 address, u32 size) +{ + std::lock_guard guard(m_lock); + for (auto [itr, end] = modules.equal_range(module); itr != end; ++itr) + { + if (itr->second.version != version) + continue; + + AddFunction(name, address, size); + AddLabel(name, address); + itr->second.exports.push_back({address, size, 0, name}); + } +} + +std::vector SymbolMap::GetModules() const +{ + std::lock_guard guard(m_lock); + std::vector result; + for (auto& module : modules) + { + std::vector exports; + for (auto& fun : module.second.exports) + { + exports.push_back({fun.name, fun.start, fun.size}); + } + result.push_back({module.second.name, module.second.version, exports}); + } + return result; +} + +void SymbolMap::RemoveModule(const std::string& name, ModuleVersion version) +{ + std::lock_guard guard(m_lock); + for (auto [itr, end] = modules.equal_range(name); itr != end; ++itr) + { + if (itr->second.version != version) + continue; + + for (auto& exportEntry : itr->second.exports) + { + RemoveFunction(exportEntry.start); + } + + modules.erase(itr); + break; + } +} + +void SymbolMap::ClearModules() +{ + std::lock_guard guard(m_lock); + for (auto& module : modules) + { + for (auto& exportEntry : module.second.exports) + { + RemoveFunction(exportEntry.start); + } + } + modules.clear(); +} diff --git a/pcsx2/DebugTools/SymbolMap.h b/pcsx2/DebugTools/SymbolMap.h index 499ec3581f..b63d7e310e 100644 --- a/pcsx2/DebugTools/SymbolMap.h +++ b/pcsx2/DebugTools/SymbolMap.h @@ -45,12 +45,19 @@ struct SymbolEntry u32 size; }; -struct LoadedModuleInfo +struct ModuleVersion +{ + u8 major; + u8 minor; + + friend auto operator<=>(const ModuleVersion&, const ModuleVersion&) = default; +}; + +struct ModuleInfo { std::string name; - u32 address; - u32 size; - bool active; + ModuleVersion version; + std::vector exports; }; enum DataType @@ -75,7 +82,7 @@ public: bool GetSymbolInfo(SymbolInfo* info, u32 address, SymbolType symmask = ST_FUNCTION) const; u32 GetNextSymbolAddress(u32 address, SymbolType symmask); std::string GetDescription(unsigned int address) const; - std::vector GetAllSymbols(SymbolType symmask); + std::vector GetAllSymbols(SymbolType symmask) const; void AddFunction(const std::string& name, u32 address, u32 size); u32 GetFunctionStart(u32 address) const; @@ -94,6 +101,17 @@ public: u32 GetDataSize(u32 startAddress) const; DataType GetDataType(u32 startAddress) const; + // Module functions for IOP symbols + + bool AddModule(const std::string& name, ModuleVersion version); + void AddModuleExport(const std::string& module, ModuleVersion version, const std::string& name, u32 address, u32 size); + std::vector GetModules() const; + void RemoveModule(const std::string& name, ModuleVersion version); + // Clears any modules and their associated exports + // Prefer this over Clear() so we don't clear user defined functions + // In the future we should mark functions as user defined + void ClearModules(); + static const u32 INVALID_ADDRESS = (u32)-1; bool IsEmpty() const { return functions.empty() && labels.empty() && data.empty(); }; @@ -106,6 +124,7 @@ private: u32 start; u32 size; int index; + std::string name; }; struct LabelEntry @@ -121,9 +140,20 @@ private: u32 size; }; + struct ModuleEntry + { + std::string name; + ModuleVersion version; + // This is duplicated data from the function map + // The issue is that multiple exports can point to the same address + // The address we use as a key... We should use a multimap in the future + std::vector exports; + }; + std::map functions; std::map labels; std::map data; + std::multimap modules; mutable std::recursive_mutex m_lock; }; diff --git a/pcsx2/IopBios.cpp b/pcsx2/IopBios.cpp index ed84927519..d88901bb5d 100644 --- a/pcsx2/IopBios.cpp +++ b/pcsx2/IopBios.cpp @@ -25,6 +25,10 @@ #include "x86/iR3000A.h" #include "VMManager.h" +#include +#include +#include +#include #include "common/FileSystem.h" #include "common/Path.h" @@ -42,10 +46,10 @@ #endif #if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) -#define S_ISREG(m) (((m)&S_IFMT) == S_IFREG) +#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #endif #if !defined(S_ISDIR) && defined(S_IFMT) && defined(S_IFDIR) -#define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR) +#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #endif #ifndef O_BINARY @@ -1017,6 +1021,31 @@ namespace R3000A namespace loadcore { + u32 GetModList(u32 a0reg) + { + u32 lcptr = iopMemRead32(0x3f0); + u32 lcstring = irxFindLoadcore(lcptr); + u32 list = 0; + + if (lcstring == 0) + { + list = lcptr - 0x20; + } + else + { + list = lcstring + 0x18; + } + + u32 mod = iopMemRead32(list); + + while (mod != 0) + { + mod = iopMemRead32(mod); + } + + return list; + } + // Gets the thread list ptr from thbase u32 GetThreadList(u32 a0reg, u32 version) { @@ -1035,8 +1064,49 @@ namespace R3000A return list; } - int RegisterLibraryEntries_HLE() + void LoadFuncs(u32 a0reg) { + const std::string modname = iopMemReadString(a0reg + 12, 8); + ModuleVersion version = {iopMemRead8(a0 + 9), iopMemRead8(a0 + 8)}; + DevCon.WriteLn(Color_Gray, "RegisterLibraryEntries: %8.8s version %x.%02x", modname.data(), version.major, version.minor); + + if (R3000SymbolMap.AddModule(modname, version)) + { + u32 func = a0reg + 20; + u32 funcptr = iopMemRead32(func); + u32 index = 0; + while (funcptr != 0) + { + const std::string funcname = std::string(irxImportFuncname(modname, index)); + if (!funcname.empty()) + { + R3000SymbolMap.AddModuleExport(modname, version, fmt::format("{}[{:02}]::{}", modname, index, funcname).c_str(), funcptr, 0); + } + else + { + R3000SymbolMap.AddModuleExport(modname, version, fmt::format("{}[{:02}]::unkn_{:02}", modname, index, index).c_str(), funcptr, 0); + } + index++; + func += 4; + funcptr = iopMemRead32(func); + } + } + } + + void ReleaseFuncs(u32 a0reg) + { + const std::string modname = iopMemReadString(a0reg + 12, 8); + ModuleVersion version = {iopMemRead8(a0 + 9), iopMemRead8(a0 + 8)}; + + DevCon.WriteLn(Color_Gray, "ReleaseLibraryEntries: %8.8s version %x.%02x", modname.data(), version.major, version.minor); + + R3000SymbolMap.RemoveModule(modname, version); + } + + void RegisterLibraryEntries_DEBUG() + { + LoadFuncs(a0); + const std::string modname = iopMemReadString(a0 + 12); if (modname == "thbase") { @@ -1044,13 +1114,12 @@ namespace R3000A CurrentBiosInformation.iopThreadListAddr = GetThreadList(a0, version); } - return 0; + CurrentBiosInformation.iopModListAddr = GetModList(a0); } - void RegisterLibraryEntries_DEBUG() + void ReleaseLibraryEntries_DEBUG() { - const std::string modname = iopMemReadString(a0 + 12); - DevCon.WriteLn(Color_Gray, "RegisterLibraryEntries: %8.8s version %x.%02x", modname.data(), (unsigned)iopMemRead8(a0 + 9), (unsigned)iopMemRead8(a0 + 8)); + ReleaseFuncs(a0); } } // namespace loadcore @@ -1099,6 +1168,24 @@ namespace R3000A } } // namespace sifcmd + u32 irxFindLoadcore(u32 entrypc) + { + u32 i; + + i = entrypc; + while (entrypc - i < 0x50) + { + // find loadcore string + if (iopMemRead32(i) == 0x49497350 && iopMemRead32(i + 4) == 0x64616F6C) + { + return i; + } + i -= 4; + } + + return 0; + } + u32 irxImportTableAddr(u32 entrypc) { u32 i; @@ -1128,10 +1215,10 @@ namespace R3000A // case 3: ??? } - return 0; + return ""; } -// clang-format off + // clang-format off #define MODULE(n) \ if (#n == libname) \ { \ @@ -1157,11 +1244,6 @@ namespace R3000A EXPORT_H( 14, Kprintf) END_MODULE - // For grabbing the thread list from thbase - MODULE(loadcore) - EXPORT_H( 6, RegisterLibraryEntries) - END_MODULE - // Special case with ioman and iomanX // They are mostly compatible excluding stat structures if(libname == "ioman" || libname == "iomanx") @@ -1201,6 +1283,7 @@ namespace R3000A // clang-format off MODULE(loadcore) EXPORT_D( 6, RegisterLibraryEntries) + EXPORT_D( 7, ReleaseLibraryEntries); END_MODULE MODULE(intrman) EXPORT_D( 4, RegisterIntrHandler) diff --git a/pcsx2/IopBios.h b/pcsx2/IopBios.h index e433e8f4ec..92110cf217 100644 --- a/pcsx2/IopBios.h +++ b/pcsx2/IopBios.h @@ -71,6 +71,7 @@ typedef void (*irxDEBUG)(); namespace R3000A { + u32 irxFindLoadcore(u32 entrypc); u32 irxImportTableAddr(u32 entrypc); const char* irxImportFuncname(const std::string& libname, u16 index); irxHLE irxImportHLE(const std::string& libnam, u16 index); diff --git a/pcsx2/R3000AInterpreter.cpp b/pcsx2/R3000AInterpreter.cpp index 5c6af6d27d..10a349f0c4 100644 --- a/pcsx2/R3000AInterpreter.cpp +++ b/pcsx2/R3000AInterpreter.cpp @@ -249,6 +249,15 @@ static void doBranch(s32 tar) { if (tar == 0x0) DevCon.Warning("[R3000 Interpreter] Warning: Branch to 0x0!"); + // When upgrading the IOP, there are two resets, the second of which is a 'fake' reset + // This second 'reset' involves UDNL calling SYSMEM and LOADCORE directly, resetting LOADCORE's modules + // This detects when SYSMEM is called and clears the modules then + if(tar == 0x890) + { + DevCon.WriteLn(Color_Gray, "[R3000 Debugger] Branch to 0x890 (SYSMEM). Clearing modules."); + R3000SymbolMap.ClearModules(); + } + branch2 = iopIsDelaySlot = true; branchPC = tar; execI(); diff --git a/pcsx2/x86/iR3000A.cpp b/pcsx2/x86/iR3000A.cpp index 766262f514..89d4665c4a 100644 --- a/pcsx2/x86/iR3000A.cpp +++ b/pcsx2/x86/iR3000A.cpp @@ -1537,6 +1537,15 @@ static void iopRecRecompile(const u32 startpc) u32 i; u32 willbranch3 = 0; + // When upgrading the IOP, there are two resets, the second of which is a 'fake' reset + // This second 'reset' involves UDNL calling SYSMEM and LOADCORE directly, resetting LOADCORE's modules + // This detects when SYSMEM is called and clears the modules then + if(startpc == 0x890) + { + DevCon.WriteLn(Color_Gray, "[R3000 Debugger] Branch to 0x890 (SYSMEM). Clearing modules."); + R3000SymbolMap.ClearModules(); + } + // Inject IRX hack if (startpc == 0x1630 && EmuConfig.CurrentIRX.length() > 3) {