From 42cefdbff0dd1127a008aa29f9ab4eaaf437012d Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Mon, 22 Jul 2019 20:49:39 +0800 Subject: [PATCH] citra_qt/debugger: Add recorder widget This widget provides a simple list of recorded requests as well as a simple filter and the 'Clear' button. Requests with status 'HLE Unimplemented' or 'Error' will be marked out with the red color. This widget handles retrival of service name. For reasons refer to a previous commit message. Added the widget to the Debugging menu, pretty much the same as other debugging widgets. --- src/citra_qt/CMakeLists.txt | 3 + src/citra_qt/debugger/ipc/recorder.cpp | 183 +++++++++++++++++++++++++ src/citra_qt/debugger/ipc/recorder.h | 52 +++++++ src/citra_qt/debugger/ipc/recorder.ui | 93 +++++++++++++ src/citra_qt/main.cpp | 8 ++ src/citra_qt/main.h | 2 + 6 files changed, 341 insertions(+) create mode 100644 src/citra_qt/debugger/ipc/recorder.cpp create mode 100644 src/citra_qt/debugger/ipc/recorder.h create mode 100644 src/citra_qt/debugger/ipc/recorder.ui diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 9d5137a4f..0198ada76 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -93,6 +93,9 @@ add_executable(citra-qt debugger/ipc/record_dialog.cpp debugger/ipc/record_dialog.h debugger/ipc/record_dialog.ui + debugger/ipc/recorder.cpp + debugger/ipc/recorder.h + debugger/ipc/recorder.ui debugger/lle_service_modules.cpp debugger/lle_service_modules.h debugger/profiler.cpp diff --git a/src/citra_qt/debugger/ipc/recorder.cpp b/src/citra_qt/debugger/ipc/recorder.cpp new file mode 100644 index 000000000..24128e38a --- /dev/null +++ b/src/citra_qt/debugger/ipc/recorder.cpp @@ -0,0 +1,183 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include "citra_qt/debugger/ipc/record_dialog.h" +#include "citra_qt/debugger/ipc/recorder.h" +#include "common/assert.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/hle/kernel/ipc_debugger/recorder.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/service/sm/sm.h" +#include "ui_recorder.h" + +IPCRecorderWidget::IPCRecorderWidget(QWidget* parent) + : QDockWidget(parent), ui(std::make_unique()) { + + ui->setupUi(this); + qRegisterMetaType(); + + connect(ui->enabled, &QCheckBox::stateChanged, + [this](int new_state) { SetEnabled(new_state == Qt::Checked); }); + connect(ui->clearButton, &QPushButton::clicked, this, &IPCRecorderWidget::Clear); + connect(ui->filter, &QLineEdit::textChanged, this, &IPCRecorderWidget::ApplyFilterToAll); + connect(ui->main, &QTreeWidget::itemDoubleClicked, this, &IPCRecorderWidget::OpenRecordDialog); + connect(this, &IPCRecorderWidget::EntryUpdated, this, &IPCRecorderWidget::OnEntryUpdated); +} + +IPCRecorderWidget::~IPCRecorderWidget() = default; + +void IPCRecorderWidget::OnEmulationStarting() { + Clear(); + id_offset = 1; + + // Update the enabled status when the system is powered on. + SetEnabled(ui->enabled->isChecked()); +} + +QString IPCRecorderWidget::GetStatusStr(const IPCDebugger::RequestRecord& record) const { + switch (record.status) { + case IPCDebugger::RequestStatus::Invalid: + return tr("Invalid"); + case IPCDebugger::RequestStatus::Sent: + return tr("Sent"); + case IPCDebugger::RequestStatus::Handling: + return tr("Handling"); + case IPCDebugger::RequestStatus::Handled: + if (record.translated_reply_cmdbuf[1] == RESULT_SUCCESS.raw) { + return tr("Success"); + } + return tr("Error"); + case IPCDebugger::RequestStatus::HLEUnimplemented: + return tr("HLE Unimplemented"); + default: + UNREACHABLE(); + } +} + +void IPCRecorderWidget::OnEntryUpdated(IPCDebugger::RequestRecord record) { + if (record.id < id_offset) { // The record has already been deleted by 'Clear' + return; + } + + QString service = GetServiceName(record); + if (record.status == IPCDebugger::RequestStatus::Handling || + record.status == IPCDebugger::RequestStatus::Handled || + record.status == IPCDebugger::RequestStatus::HLEUnimplemented) { + + service = QStringLiteral("%1 (%2)").arg(service, record.is_hle ? tr("HLE") : tr("LLE")); + } + + QTreeWidgetItem item{ + {QString::number(record.id), GetStatusStr(record), service, GetFunctionName(record)}}; + + const int row_id = record.id - id_offset; + if (ui->main->invisibleRootItem()->childCount() > row_id) { + records[row_id] = record; + (*ui->main->invisibleRootItem()->child(row_id)) = item; + } else { + records.emplace_back(record); + ui->main->invisibleRootItem()->addChild(new QTreeWidgetItem(item)); + } + + if (record.status == IPCDebugger::RequestStatus::HLEUnimplemented || + (record.status == IPCDebugger::RequestStatus::Handled && + record.translated_reply_cmdbuf[1] != RESULT_SUCCESS.raw)) { // Unimplemented / Error + + auto* item = ui->main->invisibleRootItem()->child(row_id); + for (int column = 0; column < item->columnCount(); ++column) { + item->setBackgroundColor(column, QColor::fromRgb(255, 0, 0)); + } + } + + ApplyFilter(row_id); +} + +void IPCRecorderWidget::SetEnabled(bool enabled) { + if (!Core::System::GetInstance().IsPoweredOn()) { + return; + } + + auto& ipc_recorder = Core::System::GetInstance().Kernel().GetIPCRecorder(); + ipc_recorder.SetEnabled(enabled); + + if (enabled) { + handle = ipc_recorder.BindCallback( + [this](const IPCDebugger::RequestRecord& record) { emit EntryUpdated(record); }); + } else if (handle) { + ipc_recorder.UnbindCallback(handle); + } +} + +void IPCRecorderWidget::Clear() { + id_offset = records.size() + 1; + + records.clear(); + ui->main->invisibleRootItem()->takeChildren(); +} + +QString IPCRecorderWidget::GetServiceName(const IPCDebugger::RequestRecord& record) const { + if (Core::System::GetInstance().IsPoweredOn() && record.client_port.id != -1) { + const auto service_name = + Core::System::GetInstance().ServiceManager().GetServiceNameByPortId( + static_cast(record.client_port.id)); + + if (!service_name.empty()) { + return QString::fromStdString(service_name); + } + } + + // Get a similar result from the server session name + std::string session_name = record.server_session.name; + session_name = Common::ReplaceAll(session_name, "_Server", ""); + session_name = Common::ReplaceAll(session_name, "_Client", ""); + return QString::fromStdString(session_name); +} + +QString IPCRecorderWidget::GetFunctionName(const IPCDebugger::RequestRecord& record) const { + if (record.untranslated_request_cmdbuf.empty()) { // Cmdbuf is not yet available + return tr("Unknown"); + } + const QString header_code = + QStringLiteral("0x%1").arg(record.untranslated_request_cmdbuf[0], 8, 16, QLatin1Char('0')); + if (record.function_name.empty()) { + return header_code; + } + return QStringLiteral("%1 (%2)").arg(QString::fromStdString(record.function_name), header_code); +} + +void IPCRecorderWidget::ApplyFilter(int index) { + auto* item = ui->main->invisibleRootItem()->child(index); + const QString filter = ui->filter->text(); + if (filter.isEmpty()) { + item->setHidden(false); + return; + } + + for (int i = 0; i < item->columnCount(); ++i) { + if (item->text(i).contains(filter)) { + item->setHidden(false); + return; + } + } + + item->setHidden(true); +} + +void IPCRecorderWidget::ApplyFilterToAll() { + for (int i = 0; i < ui->main->invisibleRootItem()->childCount(); ++i) { + ApplyFilter(i); + } +} + +void IPCRecorderWidget::OpenRecordDialog(QTreeWidgetItem* item, [[maybe_unused]] int column) { + int index = ui->main->invisibleRootItem()->indexOfChild(item); + + RecordDialog dialog(this, records[static_cast(index)], item->text(2), + item->text(3)); + dialog.exec(); +} diff --git a/src/citra_qt/debugger/ipc/recorder.h b/src/citra_qt/debugger/ipc/recorder.h new file mode 100644 index 000000000..63b190790 --- /dev/null +++ b/src/citra_qt/debugger/ipc/recorder.h @@ -0,0 +1,52 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include "core/hle/kernel/ipc_debugger/recorder.h" + +class QTreeWidgetItem; + +namespace Ui { +class IPCRecorder; +} + +class IPCRecorderWidget : public QDockWidget { + Q_OBJECT + +public: + explicit IPCRecorderWidget(QWidget* parent = nullptr); + ~IPCRecorderWidget(); + + void OnEmulationStarting(); + +signals: + void EntryUpdated(IPCDebugger::RequestRecord record); + +private: + QString GetStatusStr(const IPCDebugger::RequestRecord& record) const; + void OnEntryUpdated(IPCDebugger::RequestRecord record); + void SetEnabled(bool enabled); + void Clear(); + void ApplyFilter(int index); + void ApplyFilterToAll(); + QString GetServiceName(const IPCDebugger::RequestRecord& record) const; + QString GetFunctionName(const IPCDebugger::RequestRecord& record) const; + void OpenRecordDialog(QTreeWidgetItem* item, int column); + + std::unique_ptr ui; + IPCDebugger::CallbackHandle handle; + + // The offset between record id and row id, assuming record ids are assigned + // continuously and only the 'Clear' action can be performed, this is enough. + // The initial value is 1, which means record 1 = row 0. + int id_offset = 1; + std::vector records; +}; + +Q_DECLARE_METATYPE(IPCDebugger::RequestRecord); diff --git a/src/citra_qt/debugger/ipc/recorder.ui b/src/citra_qt/debugger/ipc/recorder.ui new file mode 100644 index 000000000..78b988fdc --- /dev/null +++ b/src/citra_qt/debugger/ipc/recorder.ui @@ -0,0 +1,93 @@ + + + IPCRecorder + + + + 0 + 0 + 600 + 300 + + + + IPC Recorder + + + + + + + Enable Recording + + + + + + + + + Filter: + + + + + + + Leave empty to disable filtering + + + + + + + + + true + + + + # + + + + + Status + + + + + Service + + + + + Function + + + + + + + + + + Qt::Horizontal + + + + + + + Clear + + + + + + + + + + + diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index d1a31fa5c..49e4588ae 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -36,6 +36,7 @@ #include "citra_qt/debugger/graphics/graphics_surface.h" #include "citra_qt/debugger/graphics/graphics_tracing.h" #include "citra_qt/debugger/graphics/graphics_vertex_shader.h" +#include "citra_qt/debugger/ipc/recorder.h" #include "citra_qt/debugger/lle_service_modules.h" #include "citra_qt/debugger/profiler.h" #include "citra_qt/debugger/registers.h" @@ -328,6 +329,13 @@ void GMainWindow::InitializeDebugWidgets() { [this] { lleServiceModulesWidget->setDisabled(true); }); connect(this, &GMainWindow::EmulationStopping, waitTreeWidget, [this] { lleServiceModulesWidget->setDisabled(false); }); + + ipcRecorderWidget = new IPCRecorderWidget(this); + addDockWidget(Qt::RightDockWidgetArea, ipcRecorderWidget); + ipcRecorderWidget->hide(); + debug_menu->addAction(ipcRecorderWidget->toggleViewAction()); + connect(this, &GMainWindow::EmulationStarting, ipcRecorderWidget, + &IPCRecorderWidget::OnEmulationStarting); } void GMainWindow::InitializeRecentFileMenuActions() { diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index 780589141..c5861cb2e 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -30,6 +30,7 @@ class GraphicsBreakPointsWidget; class GraphicsTracingWidget; class GraphicsVertexShaderWidget; class GRenderWindow; +class IPCRecorderWidget; class LLEServiceModulesWidget; class MicroProfileDialog; class MultiplayerState; @@ -247,6 +248,7 @@ private: GraphicsBreakPointsWidget* graphicsBreakpointsWidget; GraphicsVertexShaderWidget* graphicsVertexShaderWidget; GraphicsTracingWidget* graphicsTracingWidget; + IPCRecorderWidget* ipcRecorderWidget; LLEServiceModulesWidget* lleServiceModulesWidget; WaitTreeWidget* waitTreeWidget; Updater* updater;