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.
This commit is contained in:
zhupengfei 2019-07-22 20:49:39 +08:00
parent 45930e0621
commit 42cefdbff0
6 changed files with 341 additions and 0 deletions

View file

@ -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

View file

@ -0,0 +1,183 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QString>
#include <QTreeWidgetItem>
#include <fmt/format.h>
#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::IPCRecorder>()) {
ui->setupUi(this);
qRegisterMetaType<IPCDebugger::RequestRecord>();
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<u32>(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<std::size_t>(index)], item->text(2),
item->text(3));
dialog.exec();
}

View file

@ -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 <map>
#include <memory>
#include <unordered_map>
#include <QDockWidget>
#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::IPCRecorder> 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<IPCDebugger::RequestRecord> records;
};
Q_DECLARE_METATYPE(IPCDebugger::RequestRecord);

View file

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>IPCRecorder</class>
<widget class="QDockWidget" name="IPCRecorder">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>IPC Recorder</string>
</property>
<widget class="QWidget">
<layout class="QVBoxLayout">
<item>
<widget class="QCheckBox" name="enabled">
<property name="text">
<string>Enable Recording</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel">
<property name="text">
<string>Filter:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="filter">
<property name="placeholderText">
<string>Leave empty to disable filtering</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTreeWidget" name="main">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<column>
<property name="text">
<string>#</string>
</property>
</column>
<column>
<property name="text">
<string>Status</string>
</property>
</column>
<column>
<property name="text">
<string>Service</string>
</property>
</column>
<column>
<property name="text">
<string>Function</string>
</property>
</column>
</widget>
</item>
<item>
<layout class="QHBoxLayout">
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="clearButton">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -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() {

View file

@ -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;