Debugger: IOP Function Tree

Co-authored-by: Ziemas <ziemas@ziemas.se>
This commit is contained in:
Ty Lamontagne 2023-10-26 18:26:16 -04:00 committed by refractionpcsx2
parent 85539c7bb9
commit 46a0c2d5b6
10 changed files with 468 additions and 61 deletions

View File

@ -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.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::customContextMenuRequested, this, &CpuWidget::onFuncListContextMenu);
connect(m_ui.listFunctions, &QListWidget::itemDoubleClicked, this, &CpuWidget::onFuncListDoubleClick); 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.btnRefreshFunctions, &QPushButton::clicked, [this] { updateFunctionList(); });
connect(m_ui.txtFuncSearch, &QLineEdit::textChanged, [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.registerWidget->SetCpu(&cpu);
m_ui.memoryviewWidget->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(); this->repaint();
} }
@ -396,7 +407,7 @@ void CpuWidget::contextBPListPasteCSV()
} }
// Condition // Condition
if (fields[4] != "No Condition") if (!fields[4].isEmpty())
{ {
PostfixExpression expr; PostfixExpression expr;
bp.hasCond = true; bp.hasCond = true;
@ -467,36 +478,82 @@ void CpuWidget::updateFunctionList(bool whenEmpty)
if (!m_cpu.isAlive()) if (!m_cpu.isAlive())
return; return;
if (whenEmpty && m_ui.listFunctions->count()) if (m_cpu.getCpuType() == BREAKPOINT_EE || !m_moduleView)
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))
{ {
QString symbolName = symbol.name.c_str(); if (whenEmpty && m_ui.listFunctions->count())
if (m_demangleFunctions) 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 // 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 // Fall back to the original name if this is the case
if (symbolName.isEmpty()) if (symbolName.isEmpty())
symbolName = symbol.name.c_str(); 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)) m_ui.treeModules->clear();
continue; 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<QTreeWidgetItem*> 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)); if (!filter.isEmpty() && functions.size())
{
item->setData(256, symbol.address); moduleItem->setExpanded(true);
m_ui.treeModules->insertTopLevelItem(0, moduleItem);
m_ui.listFunctions->addItem(item); }
else if (filter.isEmpty())
{
m_ui.treeModules->insertTopLevelItem(0, moduleItem);
}
else
{
delete moduleItem;
}
}
} }
} }
@ -555,19 +612,6 @@ void CpuWidget::onFuncListContextMenu(QPoint pos)
else else
m_funclistContextMenu->clear(); 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); QAction* copyName = new QAction(tr("Copy Function Name"), m_ui.listFunctions);
connect(copyName, &QAction::triggered, [this] { connect(copyName, &QAction::triggered, [this] {
// We only store the address in the widget item // 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_ui.memoryviewWidget->gotoAddress(m_ui.listFunctions->selectedItems().first()->data(256).toUInt());
}); });
m_funclistContextMenu->addSeparator();
m_funclistContextMenu->addAction(gotoMemory); 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)); 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()); 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() void CpuWidget::updateStackFrames()
{ {
m_stackModel.refreshData(); m_stackModel.refreshData();

View File

@ -74,7 +74,8 @@ public slots:
void onFuncListContextMenu(QPoint pos); void onFuncListContextMenu(QPoint pos);
void onFuncListDoubleClick(QListWidgetItem* item); void onFuncListDoubleClick(QListWidgetItem* item);
bool getDemangleFunctions() const { return m_demangleFunctions; } bool getDemangleFunctions() const { return m_demangleFunctions; }
void onModuleTreeContextMenu(QPoint pos);
void onModuleTreeDoubleClick(QTreeWidgetItem* item);
void reloadCPUWidgets() void reloadCPUWidgets()
{ {
if (!QtHost::IsOnUIThread()) if (!QtHost::IsOnUIThread())
@ -99,6 +100,7 @@ private:
QMenu* m_stacklistContextMenu = 0; QMenu* m_stacklistContextMenu = 0;
QMenu* m_funclistContextMenu = 0; QMenu* m_funclistContextMenu = 0;
QMenu* m_moduleTreeContextMenu = 0;
Ui::CpuWidget m_ui; Ui::CpuWidget m_ui;
@ -110,4 +112,5 @@ private:
StackModel m_stackModel; StackModel m_stackModel;
bool m_demangleFunctions = true; bool m_demangleFunctions = true;
bool m_moduleView = true;
}; };

View File

@ -115,6 +115,40 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item>
<widget class="QTreeWidget" name="treeModules">
<property name="enabled">
<bool>true</bool>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="indentation">
<number>8</number>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="columnCount">
<number>3</number>
</property>
<column>
<property name="text">
<string>Module</string>
</property>
</column>
<column>
<property name="text">
<string>Version</string>
</property>
</column>
<column>
<property name="text">
<string>Count</string>
</property>
</column>
</widget>
</item>
<item> <item>
<widget class="QListWidget" name="listFunctions"> <widget class="QListWidget" name="listFunctions">
<property name="contextMenuPolicy"> <property name="contextMenuPolicy">
@ -324,7 +358,7 @@
</size> </size>
</property> </property>
<property name="tabPosition"> <property name="tabPosition">
<enum>QTabWidget::South</enum> <enum>QTabWidget::North</enum>
</property> </property>
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>0</number>

View File

@ -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) // 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(); return m_cpu.disasm(bp->addr, true).c_str();
case BreakpointColumns::CONDITION: 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: case BreakpointColumns::HITS:
return tr("--"); return tr("--");
case BreakpointColumns::ENABLED: case BreakpointColumns::ENABLED:
@ -235,7 +235,7 @@ QVariant BreakpointModel::headerData(int section, Qt::Orientation orientation, i
return tr("HITS"); return tr("HITS");
case BreakpointColumns::ENABLED: case BreakpointColumns::ENABLED:
//: Warning: limited space available. Abbreviate if needed. //: Warning: limited space available. Abbreviate if needed.
return tr("ENABLED"); return tr("X");
default: default:
return QVariant(); return QVariant();
} }

View File

@ -40,6 +40,7 @@ void SymbolMap::Clear()
functions.clear(); functions.clear();
labels.clear(); labels.clear();
data.clear(); data.clear();
modules.clear();
} }
@ -222,7 +223,7 @@ std::string SymbolMap::GetDescription(unsigned int address) const
return descriptionTemp; return descriptionTemp;
} }
std::vector<SymbolEntry> SymbolMap::GetAllSymbols(SymbolType symmask) std::vector<SymbolEntry> SymbolMap::GetAllSymbols(SymbolType symmask) const
{ {
std::vector<SymbolEntry> result; std::vector<SymbolEntry> result;
@ -273,6 +274,7 @@ void SymbolMap::AddFunction(const std::string& name, u32 address, u32 size)
func.start = address; func.start = address;
func.size = size; func.size = size;
func.index = (int)functions.size(); func.index = (int)functions.size();
func.name = name;
functions[address] = func; functions[address] = func;
functions.insert(std::make_pair(address, func)); functions.insert(std::make_pair(address, func));
@ -509,3 +511,91 @@ DataType SymbolMap::GetDataType(u32 startAddress) const
return DATATYPE_NONE; return DATATYPE_NONE;
return it->second.type; return it->second.type;
} }
bool SymbolMap::AddModule(const std::string& name, ModuleVersion version)
{
std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> 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<ModuleInfo> SymbolMap::GetModules() const
{
std::lock_guard<std::recursive_mutex> guard(m_lock);
std::vector<ModuleInfo> result;
for (auto& module : modules)
{
std::vector<SymbolEntry> 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<std::recursive_mutex> 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<std::recursive_mutex> guard(m_lock);
for (auto& module : modules)
{
for (auto& exportEntry : module.second.exports)
{
RemoveFunction(exportEntry.start);
}
}
modules.clear();
}

View File

@ -45,12 +45,19 @@ struct SymbolEntry
u32 size; u32 size;
}; };
struct LoadedModuleInfo struct ModuleVersion
{
u8 major;
u8 minor;
friend auto operator<=>(const ModuleVersion&, const ModuleVersion&) = default;
};
struct ModuleInfo
{ {
std::string name; std::string name;
u32 address; ModuleVersion version;
u32 size; std::vector<SymbolEntry> exports;
bool active;
}; };
enum DataType enum DataType
@ -75,7 +82,7 @@ public:
bool GetSymbolInfo(SymbolInfo* info, u32 address, SymbolType symmask = ST_FUNCTION) const; bool GetSymbolInfo(SymbolInfo* info, u32 address, SymbolType symmask = ST_FUNCTION) const;
u32 GetNextSymbolAddress(u32 address, SymbolType symmask); u32 GetNextSymbolAddress(u32 address, SymbolType symmask);
std::string GetDescription(unsigned int address) const; std::string GetDescription(unsigned int address) const;
std::vector<SymbolEntry> GetAllSymbols(SymbolType symmask); std::vector<SymbolEntry> GetAllSymbols(SymbolType symmask) const;
void AddFunction(const std::string& name, u32 address, u32 size); void AddFunction(const std::string& name, u32 address, u32 size);
u32 GetFunctionStart(u32 address) const; u32 GetFunctionStart(u32 address) const;
@ -94,6 +101,17 @@ public:
u32 GetDataSize(u32 startAddress) const; u32 GetDataSize(u32 startAddress) const;
DataType GetDataType(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<ModuleInfo> 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; static const u32 INVALID_ADDRESS = (u32)-1;
bool IsEmpty() const { return functions.empty() && labels.empty() && data.empty(); }; bool IsEmpty() const { return functions.empty() && labels.empty() && data.empty(); };
@ -106,6 +124,7 @@ private:
u32 start; u32 start;
u32 size; u32 size;
int index; int index;
std::string name;
}; };
struct LabelEntry struct LabelEntry
@ -121,9 +140,20 @@ private:
u32 size; 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<FunctionEntry> exports;
};
std::map<u32, FunctionEntry> functions; std::map<u32, FunctionEntry> functions;
std::map<u32, LabelEntry> labels; std::map<u32, LabelEntry> labels;
std::map<u32, DataEntry> data; std::map<u32, DataEntry> data;
std::multimap<std::string, ModuleEntry> modules;
mutable std::recursive_mutex m_lock; mutable std::recursive_mutex m_lock;
}; };

View File

@ -25,6 +25,10 @@
#include "x86/iR3000A.h" #include "x86/iR3000A.h"
#include "VMManager.h" #include "VMManager.h"
#include <ctype.h>
#include <fmt/format.h>
#include <string.h>
#include <sys/stat.h>
#include "common/FileSystem.h" #include "common/FileSystem.h"
#include "common/Path.h" #include "common/Path.h"
@ -42,10 +46,10 @@
#endif #endif
#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) #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 #endif
#if !defined(S_ISDIR) && defined(S_IFMT) && defined(S_IFDIR) #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 #endif
#ifndef O_BINARY #ifndef O_BINARY
@ -1017,6 +1021,31 @@ namespace R3000A
namespace loadcore 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 // Gets the thread list ptr from thbase
u32 GetThreadList(u32 a0reg, u32 version) u32 GetThreadList(u32 a0reg, u32 version)
{ {
@ -1035,8 +1064,49 @@ namespace R3000A
return list; 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); const std::string modname = iopMemReadString(a0 + 12);
if (modname == "thbase") if (modname == "thbase")
{ {
@ -1044,13 +1114,12 @@ namespace R3000A
CurrentBiosInformation.iopThreadListAddr = GetThreadList(a0, version); CurrentBiosInformation.iopThreadListAddr = GetThreadList(a0, version);
} }
return 0; CurrentBiosInformation.iopModListAddr = GetModList(a0);
} }
void RegisterLibraryEntries_DEBUG() void ReleaseLibraryEntries_DEBUG()
{ {
const std::string modname = iopMemReadString(a0 + 12); ReleaseFuncs(a0);
DevCon.WriteLn(Color_Gray, "RegisterLibraryEntries: %8.8s version %x.%02x", modname.data(), (unsigned)iopMemRead8(a0 + 9), (unsigned)iopMemRead8(a0 + 8));
} }
} // namespace loadcore } // namespace loadcore
@ -1099,6 +1168,24 @@ namespace R3000A
} }
} // namespace sifcmd } // 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 irxImportTableAddr(u32 entrypc)
{ {
u32 i; u32 i;
@ -1128,10 +1215,10 @@ namespace R3000A
// case 3: ??? // case 3: ???
} }
return 0; return "";
} }
// clang-format off // clang-format off
#define MODULE(n) \ #define MODULE(n) \
if (#n == libname) \ if (#n == libname) \
{ \ { \
@ -1157,11 +1244,6 @@ namespace R3000A
EXPORT_H( 14, Kprintf) EXPORT_H( 14, Kprintf)
END_MODULE END_MODULE
// For grabbing the thread list from thbase
MODULE(loadcore)
EXPORT_H( 6, RegisterLibraryEntries)
END_MODULE
// Special case with ioman and iomanX // Special case with ioman and iomanX
// They are mostly compatible excluding stat structures // They are mostly compatible excluding stat structures
if(libname == "ioman" || libname == "iomanx") if(libname == "ioman" || libname == "iomanx")
@ -1201,6 +1283,7 @@ namespace R3000A
// clang-format off // clang-format off
MODULE(loadcore) MODULE(loadcore)
EXPORT_D( 6, RegisterLibraryEntries) EXPORT_D( 6, RegisterLibraryEntries)
EXPORT_D( 7, ReleaseLibraryEntries);
END_MODULE END_MODULE
MODULE(intrman) MODULE(intrman)
EXPORT_D( 4, RegisterIntrHandler) EXPORT_D( 4, RegisterIntrHandler)

View File

@ -71,6 +71,7 @@ typedef void (*irxDEBUG)();
namespace R3000A namespace R3000A
{ {
u32 irxFindLoadcore(u32 entrypc);
u32 irxImportTableAddr(u32 entrypc); u32 irxImportTableAddr(u32 entrypc);
const char* irxImportFuncname(const std::string& libname, u16 index); const char* irxImportFuncname(const std::string& libname, u16 index);
irxHLE irxImportHLE(const std::string& libnam, u16 index); irxHLE irxImportHLE(const std::string& libnam, u16 index);

View File

@ -249,6 +249,15 @@ static void doBranch(s32 tar) {
if (tar == 0x0) if (tar == 0x0)
DevCon.Warning("[R3000 Interpreter] Warning: Branch to 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; branch2 = iopIsDelaySlot = true;
branchPC = tar; branchPC = tar;
execI(); execI();

View File

@ -1537,6 +1537,15 @@ static void iopRecRecompile(const u32 startpc)
u32 i; u32 i;
u32 willbranch3 = 0; 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 // Inject IRX hack
if (startpc == 0x1630 && EmuConfig.CurrentIRX.length() > 3) if (startpc == 0x1630 && EmuConfig.CurrentIRX.length() > 3)
{ {