diff options
| author | Sepalani <sepalani@hotmail.fr> | 2020-03-21 11:48:49 +0400 |
|---|---|---|
| committer | Sepalani <sepalani@hotmail.fr> | 2020-05-03 20:48:30 +0400 |
| commit | 39d34e133f0fe6a11db11e523cfbeaf9faf9b2a1 (patch) | |
| tree | 4921345c657fa310610cbb6d19eae5794e754024 /Source/Core/DolphinQt/Debugger/ThreadWidget.cpp | |
| parent | 9ffa72ad1fb8574ecda9e120d8a7fb930d053b01 (diff) | |
Debugger: Add a Thread widget
DebugInterface: Add GetThreads
WatchWidget: Update widget on AddWatch
Diffstat (limited to 'Source/Core/DolphinQt/Debugger/ThreadWidget.cpp')
| -rw-r--r-- | Source/Core/DolphinQt/Debugger/ThreadWidget.cpp | 475 |
1 files changed, 475 insertions, 0 deletions
diff --git a/Source/Core/DolphinQt/Debugger/ThreadWidget.cpp b/Source/Core/DolphinQt/Debugger/ThreadWidget.cpp new file mode 100644 index 0000000000..3d943b2f0c --- /dev/null +++ b/Source/Core/DolphinQt/Debugger/ThreadWidget.cpp @@ -0,0 +1,475 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt/Debugger/ThreadWidget.h" + +#include <QGroupBox> +#include <QHeaderView> +#include <QLabel> +#include <QLineEdit> +#include <QMenu> +#include <QTableWidget> +#include <QVBoxLayout> + +#include "Common/BitUtils.h" +#include "Core/Core.h" +#include "Core/PowerPC/MMU.h" +#include "Core/PowerPC/PowerPC.h" +#include "DolphinQt/Host.h" +#include "DolphinQt/Settings.h" + +ThreadWidget::ThreadWidget(QWidget* parent) : QDockWidget(parent) +{ + setWindowTitle(tr("Threads")); + setObjectName(QStringLiteral("threads")); + + setHidden(!Settings::Instance().IsThreadsVisible() || !Settings::Instance().IsDebugModeEnabled()); + + setAllowedAreas(Qt::AllDockWidgetAreas); + + CreateWidgets(); + + auto& settings = Settings::GetQSettings(); + + restoreGeometry(settings.value(QStringLiteral("threadwidget/geometry")).toByteArray()); + // macOS: setHidden() needs to be evaluated before setFloating() for proper window presentation + // according to Settings + setFloating(settings.value(QStringLiteral("threadwidget/floating")).toBool()); + + ConnectWidgets(); + + connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, &ThreadWidget::Update); + + connect(&Settings::Instance(), &Settings::ThreadsVisibilityChanged, + [this](bool visible) { setHidden(!visible); }); + + connect(&Settings::Instance(), &Settings::DebugModeToggled, [this](bool enabled) { + setHidden(!enabled || !Settings::Instance().IsThreadsVisible()); + }); +} + +ThreadWidget::~ThreadWidget() +{ + auto& settings = Settings::GetQSettings(); + + settings.setValue(QStringLiteral("threadwidget/geometry"), saveGeometry()); + settings.setValue(QStringLiteral("threadwidget/floating"), isFloating()); +} + +void ThreadWidget::closeEvent(QCloseEvent*) +{ + Settings::Instance().SetThreadsVisible(false); +} + +void ThreadWidget::showEvent(QShowEvent* event) +{ + Update(); +} + +void ThreadWidget::CreateWidgets() +{ + m_state = new QGroupBox(tr("State")); + + auto* state_layout = new QHBoxLayout; + m_state->setLayout(state_layout); + state_layout->addWidget(CreateContextGroup()); + state_layout->addWidget(CreateActiveThreadQueueGroup()); + state_layout->setContentsMargins(2, 2, 2, 2); + state_layout->setSpacing(1); + + auto* widget = new QWidget; + auto* layout = new QVBoxLayout; + widget->setLayout(layout); + layout->addWidget(m_state); + layout->addWidget(CreateThreadGroup()); + layout->addWidget(CreateThreadContextGroup()); + layout->addWidget(CreateThreadCallstackGroup()); + layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding)); + setWidget(widget); + + Update(); + UpdateThreadContext({}); +} + +void ThreadWidget::ConnectWidgets() +{ + connect(m_thread_table->selectionModel(), &QItemSelectionModel::selectionChanged, + [this](const QItemSelection& selected, const QItemSelection& deselected) { + const auto indexes = selected.indexes(); + const int row = indexes.empty() ? -1 : indexes.first().row(); + OnSelectionChanged(row); + }); + connect(m_context_table, &QTableWidget::customContextMenuRequested, + [this] { ShowContextMenu(m_context_table); }); + connect(m_callstack_table, &QTableWidget::customContextMenuRequested, + [this] { ShowContextMenu(m_callstack_table); }); +} + +void ThreadWidget::ShowContextMenu(QTableWidget* table) +{ + const auto* item = static_cast<QTableWidgetItem*>(table->currentItem()); + if (item == nullptr) + return; + + bool ok; + const u32 addr = item->text().toUInt(&ok, 16); + if (!ok) + return; + + QMenu* menu = new QMenu(this); + const QString watch_name = QStringLiteral("thread_context_%1").arg(addr, 8, 16, QLatin1Char('0')); + menu->addAction(tr("Add &breakpoint"), this, [this, addr] { emit RequestBreakpoint(addr); }); + menu->addAction(tr("Add memory breakpoint"), this, + [this, addr] { emit RequestMemoryBreakpoint(addr); }); + menu->addAction(tr("Add to &watch"), this, + [this, addr, watch_name] { emit RequestWatch(watch_name, addr); }); + menu->addAction(tr("View &memory"), this, [this, addr] { emit RequestViewInMemory(addr); }); + menu->addAction(tr("View &code"), this, [this, addr] { emit RequestViewInCode(addr); }); + menu->exec(QCursor::pos()); +} + +QLineEdit* ThreadWidget::CreateLineEdit() const +{ + QLineEdit* line_edit = new QLineEdit(QLatin1Literal("00000000")); + line_edit->setReadOnly(true); + line_edit->setFixedWidth(line_edit->fontMetrics().width(QLatin1Literal(" 00000000 "))); + return line_edit; +} + +QGroupBox* ThreadWidget::CreateContextGroup() +{ + QGroupBox* context_group = new QGroupBox(tr("Thread context")); + QGridLayout* context_layout = new QGridLayout; + context_group->setLayout(context_layout); + context_layout->addWidget(new QLabel(tr("Current context")), 0, 0); + m_current_context = CreateLineEdit(); + context_layout->addWidget(m_current_context, 0, 1); + context_layout->addWidget(new QLabel(tr("Current thread")), 1, 0); + m_current_thread = CreateLineEdit(); + context_layout->addWidget(m_current_thread, 1, 1); + context_layout->addWidget(new QLabel(tr("Default thread")), 2, 0); + m_default_thread = CreateLineEdit(); + context_layout->addWidget(m_default_thread, 2, 1); + context_layout->setSpacing(1); + return context_group; +} + +QGroupBox* ThreadWidget::CreateActiveThreadQueueGroup() +{ + QGroupBox* thread_queue_group = new QGroupBox(tr("Active thread queue")); + auto* thread_queue_layout = new QGridLayout; + thread_queue_group->setLayout(thread_queue_layout); + thread_queue_layout->addWidget(new QLabel(tr("Head")), 0, 0); + m_queue_head = CreateLineEdit(); + thread_queue_layout->addWidget(m_queue_head, 0, 1); + thread_queue_layout->addWidget(new QLabel(tr("Tail")), 1, 0); + m_queue_tail = CreateLineEdit(); + thread_queue_layout->addWidget(m_queue_tail, 1, 1); + thread_queue_layout->setSpacing(1); + return thread_queue_group; +} + +QGroupBox* ThreadWidget::CreateThreadGroup() +{ + QGroupBox* thread_group = new QGroupBox(tr("Active threads")); + QGridLayout* thread_layout = new QGridLayout; + thread_group->setLayout(thread_layout); + + m_thread_table = new QTableWidget(); + QStringList header{tr("Address"), + tr("State"), + tr("Detached"), + tr("Suspended"), + QStringLiteral("%1\n(%2)").arg(tr("Base priority"), tr("Effective priority")), + QStringLiteral("%1\n%2").arg(tr("Stack end"), tr("Stack start")), + tr("errno"), + tr("Specific")}; + m_thread_table->setColumnCount(header.size()); + + m_thread_table->setHorizontalHeaderLabels(header); + m_thread_table->setTabKeyNavigation(false); + m_thread_table->verticalHeader()->setVisible(false); + m_thread_table->setEditTriggers(QAbstractItemView::NoEditTriggers); + m_thread_table->setSelectionBehavior(QAbstractItemView::SelectRows); + m_thread_table->setSelectionMode(QAbstractItemView::SingleSelection); + m_thread_table->setWordWrap(false); + + thread_layout->addWidget(m_thread_table, 0, 0); + thread_layout->setSpacing(1); + return thread_group; +} + +QGroupBox* ThreadWidget::CreateThreadContextGroup() +{ + QGroupBox* thread_context_group = new QGroupBox(tr("Selected thread context")); + QGridLayout* thread_context_layout = new QGridLayout; + thread_context_group->setLayout(thread_context_layout); + + m_context_table = new QTableWidget(); + m_context_table->setColumnCount(8); // GPR, FPR, PSF, (GQR+others) + m_context_table->setRowCount(32); + m_context_table->setTabKeyNavigation(false); + m_context_table->horizontalHeader()->setVisible(false); + m_context_table->verticalHeader()->setVisible(false); + m_context_table->setEditTriggers(QAbstractItemView::NoEditTriggers); + m_context_table->setSelectionBehavior(QAbstractItemView::SelectItems); + m_context_table->setSelectionMode(QAbstractItemView::SingleSelection); + m_context_table->setContextMenuPolicy(Qt::CustomContextMenu); + + thread_context_layout->addWidget(m_context_table, 0, 0); + thread_context_layout->setSpacing(1); + return thread_context_group; +} + +QGroupBox* ThreadWidget::CreateThreadCallstackGroup() +{ + QGroupBox* thread_callstack_group = new QGroupBox(tr("Selected thread callstack")); + QGridLayout* thread_callstack_layout = new QGridLayout; + thread_callstack_group->setLayout(thread_callstack_layout); + + m_callstack_table = new QTableWidget(); + QStringList header{tr("Address"), tr("Back Chain"), tr("LR Save"), tr("Description")}; + m_callstack_table->setColumnCount(header.size()); + + m_callstack_table->setHorizontalHeaderLabels(header); + m_callstack_table->setRowCount(0); + m_callstack_table->setTabKeyNavigation(false); + m_callstack_table->verticalHeader()->setVisible(false); + m_callstack_table->setEditTriggers(QAbstractItemView::NoEditTriggers); + m_callstack_table->setSelectionBehavior(QAbstractItemView::SelectItems); + m_callstack_table->setSelectionMode(QAbstractItemView::SingleSelection); + m_callstack_table->setContextMenuPolicy(Qt::CustomContextMenu); + + thread_callstack_layout->addWidget(m_callstack_table, 0, 0); + thread_callstack_layout->setSpacing(1); + return thread_callstack_group; +} + +void ThreadWidget::Update() +{ + if (!isVisible()) + return; + + const auto state = Core::GetState(); + if (state == Core::State::Stopping) + { + m_thread_table->setRowCount(0); + UpdateThreadContext({}); + UpdateThreadCallstack({}); + } + if (state != Core::State::Paused) + return; + + const auto format_hex = [](u32 value) { + return QStringLiteral("%1").arg(value, 8, 16, QLatin1Char('0')); + }; + const auto format_f64_as_u64 = [](double value) { + return QStringLiteral("%1").arg(Common::BitCast<u64>(value), 16, 16, QLatin1Char('0')); + }; + const auto format_hex_from = [&format_hex](u32 addr) { + addr = PowerPC::HostIsRAMAddress(addr) ? PowerPC::HostRead_U32(addr) : 0; + return format_hex(addr); + }; + const auto get_state = [](u16 state) { + QString state_name; + switch (state) + { + case 1: + state_name = tr("READY"); + break; + case 2: + state_name = tr("RUNNING"); + break; + case 4: + state_name = tr("WAITING"); + break; + case 8: + state_name = tr("MORIBUND"); + break; + default: + state_name = tr("UNKNOWN"); + } + return QStringLiteral("%1 (%2)").arg(QString::number(state), state_name); + }; + const auto get_priority = [](u16 base, u16 effective) { + return QStringLiteral("%1 (%2)").arg(QString::number(base), QString::number(effective)); + }; + const auto get_stack = [](u32 end, u32 start) { + return QStringLiteral("%1\n%2") + .arg(end, 8, 16, QLatin1Char('0')) + .arg(start, 8, 16, QLatin1Char('0')); + }; + + // YAGCD - Section 4.2.1.4 Dolphin OS Globals + m_current_context->setText(format_hex_from(0x800000D4)); + m_current_thread->setText(format_hex_from(0x800000E4)); + m_default_thread->setText(format_hex_from(0x800000D8)); + + m_queue_head->setText(format_hex_from(0x800000DC)); + m_queue_tail->setText(format_hex_from(0x800000E0)); + + // Thread group + m_threads = PowerPC::debug_interface.GetThreads(); + int i = 0; + m_thread_table->setRowCount(i); + for (const auto& thread : m_threads) + { + m_thread_table->insertRow(i); + m_thread_table->setItem(i, 0, new QTableWidgetItem(format_hex(thread->GetAddress()))); + m_thread_table->setItem(i, 1, new QTableWidgetItem(get_state(thread->GetState()))); + m_thread_table->setItem(i, 2, new QTableWidgetItem(QString::number(thread->IsDetached()))); + m_thread_table->setItem(i, 3, new QTableWidgetItem(QString::number(thread->IsSuspended()))); + m_thread_table->setItem(i, 4, + new QTableWidgetItem(get_priority(thread->GetBasePriority(), + thread->GetEffectivePriority()))); + m_thread_table->setItem( + i, 5, new QTableWidgetItem(get_stack(thread->GetStackEnd(), thread->GetStackStart()))); + m_thread_table->setItem(i, 6, new QTableWidgetItem(QString::number(thread->GetErrno()))); + m_thread_table->setItem(i, 7, + new QTableWidgetItem(QString::fromStdString(thread->GetSpecific()))); + i += 1; + } + m_thread_table->resizeColumnsToContents(); + m_thread_table->resizeRowsToContents(); + + // Thread's context group + UpdateThreadContext({}); +} + +void ThreadWidget::UpdateThreadContext(const Common::Debug::PartialContext& context) +{ + const auto format_hex = [](const std::optional<u32>& value) { + if (!value) + return QString{}; + return QStringLiteral("%1").arg(*value, 8, 16, QLatin1Char('0')); + }; + const auto format_hex_idx = [](const auto& table, std::size_t index) { + if (!table || index >= table->size()) + return QString{}; + return QStringLiteral("%1").arg(table->at(index), 8, 16, QLatin1Char('0')); + }; + const auto format_f64_as_u64_idx = [](const auto& table, std::size_t index) { + if (!table || index >= table->size()) + return QString{}; + return QStringLiteral("%1").arg(Common::BitCast<u64>(table->at(index)), 16, 16, + QLatin1Char('0')); + }; + + m_context_table->setRowCount(0); + for (int i = 0; i < 32; i++) + { + m_context_table->insertRow(i); + m_context_table->setItem(i, 0, new QTableWidgetItem(QStringLiteral("GPR%1").arg(i))); + m_context_table->setItem(i, 1, new QTableWidgetItem(format_hex_idx(context.gpr, i))); + m_context_table->setItem(i, 2, new QTableWidgetItem(QStringLiteral("FPR%1").arg(i))); + m_context_table->setItem(i, 3, new QTableWidgetItem(format_f64_as_u64_idx(context.fpr, i))); + m_context_table->setItem(i, 4, new QTableWidgetItem(QStringLiteral("PSF%1").arg(i))); + m_context_table->setItem(i, 5, new QTableWidgetItem(format_f64_as_u64_idx(context.psf, i))); + + if (i < 8) + { + m_context_table->setItem(i, 6, new QTableWidgetItem(QStringLiteral("GQR%1").arg(i))); + m_context_table->setItem(i, 7, new QTableWidgetItem(format_hex_idx(context.gqr, i))); + continue; + } + switch (i) + { + case 8: + m_context_table->setItem(i, 6, new QTableWidgetItem(QLatin1Literal("CR"))); + m_context_table->setItem(i, 7, new QTableWidgetItem(format_hex(context.cr))); + break; + case 9: + m_context_table->setItem(i, 6, new QTableWidgetItem(QLatin1Literal("LR"))); + m_context_table->setItem(i, 7, new QTableWidgetItem(format_hex(context.lr))); + break; + case 10: + m_context_table->setItem(i, 6, new QTableWidgetItem(QLatin1Literal("CTR"))); + m_context_table->setItem(i, 7, new QTableWidgetItem(format_hex(context.ctr))); + break; + case 11: + m_context_table->setItem(i, 6, new QTableWidgetItem(QLatin1Literal("XER"))); + m_context_table->setItem(i, 7, new QTableWidgetItem(format_hex(context.xer))); + break; + case 12: + m_context_table->setItem(i, 6, new QTableWidgetItem(QLatin1Literal("FPSCR"))); + m_context_table->setItem(i, 7, new QTableWidgetItem(format_hex(context.fpscr))); + break; + case 13: + m_context_table->setItem(i, 6, new QTableWidgetItem(QLatin1Literal("SRR0"))); + m_context_table->setItem(i, 7, new QTableWidgetItem(format_hex(context.srr0))); + break; + case 14: + m_context_table->setItem(i, 6, new QTableWidgetItem(QLatin1Literal("SRR1"))); + m_context_table->setItem(i, 7, new QTableWidgetItem(format_hex(context.srr1))); + break; + case 15: + m_context_table->setItem(i, 6, new QTableWidgetItem(QLatin1Literal("DUMMY"))); + m_context_table->setItem(i, 7, new QTableWidgetItem(format_hex(context.dummy))); + break; + case 16: + m_context_table->setItem(i, 6, new QTableWidgetItem(QLatin1Literal("STATE"))); + m_context_table->setItem(i, 7, new QTableWidgetItem(format_hex(context.state))); + break; + default: + for (int j = 6; j <= 7; j++) + { + auto* disabled_item = new QTableWidgetItem(); + auto flags = disabled_item->flags(); + flags &= ~Qt::ItemIsSelectable; + flags &= ~Qt::ItemIsEditable; + flags &= ~Qt::ItemIsEnabled; + disabled_item->setFlags(flags); + disabled_item->setBackground(Qt::gray); + m_context_table->setItem(i, j, disabled_item); + } + } + } + m_context_table->resizeColumnsToContents(); +} + +void ThreadWidget::UpdateThreadCallstack(const Common::Debug::PartialContext& context) +{ + m_callstack_table->setRowCount(0); + + if (!context.gpr) + return; + + const auto format_hex = [](u32 value) { + return QStringLiteral("%1").arg(value, 8, 16, QLatin1Char('0')); + }; + + u32 sp = context.gpr->at(1); + for (int i = 0; i < 16; i++) + { + if (sp == 0 || sp == 0xffffffff || !PowerPC::HostIsRAMAddress(sp)) + break; + m_callstack_table->insertRow(i); + m_callstack_table->setItem(i, 0, new QTableWidgetItem(format_hex(sp))); + if (PowerPC::HostIsRAMAddress(sp + 4)) + { + const u32 lr_save = PowerPC::HostRead_U32(sp + 4); + m_callstack_table->setItem(i, 2, new QTableWidgetItem(format_hex(lr_save))); + m_callstack_table->setItem(i, 3, + new QTableWidgetItem(QString::fromStdString( + PowerPC::debug_interface.GetDescription(lr_save)))); + } + else + { + m_callstack_table->setItem(i, 2, new QTableWidgetItem(QLatin1Literal("--------"))); + } + sp = PowerPC::HostRead_U32(sp); + m_callstack_table->setItem(i, 1, new QTableWidgetItem(format_hex(sp))); + } +} + +void ThreadWidget::OnSelectionChanged(int row) +{ + Common::Debug::PartialContext context; + + if (row >= 0 && row < m_threads.size()) + context = m_threads[row]->GetContext(); + + UpdateThreadContext(context); + UpdateThreadCallstack(context); +} |
