Add thread debugger

This commit is contained in:
wheremyfoodat 2025-07-03 16:58:56 +03:00
parent 228068901b
commit 9932e58bf0
10 changed files with 172 additions and 14 deletions

View file

@ -1,4 +1,5 @@
#include <cstring>
#include "arm_defs.hpp"
#include "kernel.hpp"
@ -35,13 +36,14 @@ void Kernel::setupIdleThread() {
if (!vaddr.has_value() || vaddr.value() != codeAddress) {
Helpers::panic("Failed to setup idle thread");
}
// Copy idle thread code to the allocated FCRAM
std::memcpy(&mem.getFCRAM()[fcramIndex], idleThreadCode, sizeof(idleThreadCode));
t.entrypoint = codeAddress;
t.initialSP = 0;
t.tlsBase = 0;
t.gprs[13] = 0; // Set SP & LR to 0 just in case. The idle thread should never access memory, but let's be safe
t.gprs[13] = 0; // Set SP & LR to 0 just in case. The idle thread should never access memory, but let's be safe
t.gprs[14] = 0;
t.gprs[15] = codeAddress;
t.cpsr = CPSR::UserMode;

View file

@ -1,3 +1,4 @@
#include <algorithm>
#include <bit>
#include <cassert>
#include <cstring>
@ -697,3 +698,18 @@ bool Kernel::shouldWaitOnObject(KernelObject* object) {
return true;
}
}
std::vector<Thread> Kernel::getMainProcessThreads() {
// Sort the thread indices so that they appear nicer in the debugger
auto indices = threadIndices;
std::sort(indices.begin(), indices.end());
std::vector<Thread> ret;
ret.reserve(indices.size());
for (const auto& index : indices) {
ret.push_back(threads[index]);
}
return ret;
}

View file

@ -272,6 +272,11 @@ bool Emulator::loadROM(const std::filesystem::path& path) {
romType = ROMType::None;
}
if (success) {
// Update the main thread entrypoint and SP so that the thread debugger can display them.
kernel.setMainThreadEntrypointAndSP(cpu.getReg(15), cpu.getReg(13));
}
resume(); // Start the emulator
return success;
}

View file

@ -66,6 +66,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
auto cheatsEditorAction = toolsMenu->addAction(tr("Open Cheats Editor"));
auto patchWindowAction = toolsMenu->addAction(tr("Open Patch Window"));
auto shaderEditorAction = toolsMenu->addAction(tr("Open Shader Editor"));
auto threadDebuggerAction = toolsMenu->addAction(tr("Open Thread Debugger"));
auto dumpDspFirmware = toolsMenu->addAction(tr("Dump loaded DSP firmware"));
connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS);
@ -73,8 +74,11 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
connect(shaderEditorAction, &QAction::triggered, this, [this]() { shaderEditor->show(); });
connect(cheatsEditorAction, &QAction::triggered, this, [this]() { cheatsEditor->show(); });
connect(patchWindowAction, &QAction::triggered, this, [this]() { patchWindow->show(); });
connect(threadDebuggerAction, &QAction::triggered, this, [this]() { threadDebugger->show(); });
connect(dumpDspFirmware, &QAction::triggered, this, &MainWindow::dumpDspFirmware);
connect(this, &MainWindow::emulatorPaused, this, [this]() { threadDebugger->update(); }, Qt::BlockingQueuedConnection);
auto aboutAction = aboutMenu->addAction(tr("About Panda3DS"));
aboutAction->setMenuRole(QAction::AboutRole);
connect(aboutAction, &QAction::triggered, this, &MainWindow::showAboutMenu);
@ -89,6 +93,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
patchWindow = new PatchWindow(this);
luaEditor = new TextEditorWindow(this, "script.lua", "");
shaderEditor = new ShaderEditorWindow(this, "shader.glsl", "");
threadDebugger = new ThreadDebugger(emu, this);
shaderEditor->setEnable(emu->getRenderer()->supportsShaderReload());
if (shaderEditor->supported) {
@ -381,9 +386,19 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) {
delete message.cheat.c;
} break;
case MessageType::Pause: emu->pause(); break;
case MessageType::Resume: emu->resume(); break;
case MessageType::TogglePause: emu->togglePause(); break;
case MessageType::Pause:
emu->pause();
emit emulatorPaused();
break;
case MessageType::TogglePause:
emu->togglePause();
if (!emu->running) {
emit emulatorPaused();
};
break;
case MessageType::Reset: emu->reset(Emulator::ReloadOption::Reload); break;
case MessageType::PressKey: emu->getServiceManager().getHID().pressKey(message.key.key); break;
case MessageType::ReleaseKey: emu->getServiceManager().getHID().releaseKey(message.key.key); break;

View file

@ -0,0 +1,76 @@
#include "panda_qt/thread_debugger.hpp"
#include <fmt/format.h>
static QString threadStatusToQString(ThreadStatus status) {
switch (status) {
case ThreadStatus::Running: return QObject::tr("Running");
case ThreadStatus::Ready: return QObject::tr("Ready");
case ThreadStatus::WaitArbiter: return QObject::tr("Waiting on arbiter");
case ThreadStatus::WaitSleep: return QObject::tr("Sleeping");
case ThreadStatus::WaitSync1: return QObject::tr("WaitSync (1)");
case ThreadStatus::WaitSyncAny: return QObject::tr("WaitSync (Any)");
case ThreadStatus::WaitSyncAll: return QObject::tr("WaitSync (All)");
case ThreadStatus::WaitIPC: return QObject::tr("Waiting for IPC");
case ThreadStatus::Dormant: return QObject::tr("Dormant");
case ThreadStatus::Dead: return QObject::tr("Dead");
default: return QObject::tr("Unknown thread status");
}
}
ThreadDebugger::ThreadDebugger(Emulator* emu, QWidget* parent) : emu(emu), QWidget(parent, Qt::Window) {
setWindowTitle(tr("Thread Debugger"));
resize(700, 600);
mainLayout = new QVBoxLayout(this);
threadTable = new QTableWidget(this);
threadTable->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
mainLayout->addWidget(threadTable);
}
void ThreadDebugger::update() {
threadTable->clear();
threadTable->setSelectionMode(QAbstractItemView::NoSelection);
threadTable->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
threadTable->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
threadTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
threadTable->setColumnCount(5);
threadTable->verticalHeader()->setVisible(false);
threadTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
threadTable->setHorizontalHeaderLabels(QStringList({"ID", "Status", "PC", "Entrypoint", "Stack Top"}));
const auto threads = emu->getKernel().getMainProcessThreads();
const usize count = threads.size();
threadTable->setRowCount(count);
for (int i = 0; i < count; i++) {
const auto& thread = threads[i];
setListItem(i, 0, fmt::format("{}", thread.index));
setListItem(i, 1, threadStatusToQString(thread.status));
setListItem(i, 2, fmt::format("{:08X}", thread.gprs[15]));
setListItem(i, 3, fmt::format("{:08X}", thread.entrypoint));
setListItem(i, 4, fmt::format("{:08X}", thread.initialSP));
}
}
void ThreadDebugger::setListItem(int row, int column, const std::string& str) { setListItem(row, column, QString::fromStdString(str)); }
void ThreadDebugger::setListItem(int row, int column, const QString& str) {
QTableWidgetItem* item = new QTableWidgetItem();
QWidget* widget = new QWidget(this);
QVBoxLayout* layout = new QVBoxLayout(widget);
QLabel* label = new QLabel(widget);
layout->setAlignment(Qt::AlignVCenter);
layout->setContentsMargins(5, 0, 5, 0);
label->setText(str);
layout->addWidget(label);
widget->setLayout(layout);
threadTable->setItem(row, column, item);
threadTable->setCellWidget(row, column, widget);
}