mirror of https://github.com/PCSX2/pcsx2.git
Debugger: IOP Function Tree
Co-authored-by: Ziemas <ziemas@ziemas.se>
This commit is contained in:
parent
85539c7bb9
commit
46a0c2d5b6
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue