mirror of
https://github.com/Lime3DS/Lime3DS
synced 2024-12-26 09:02:44 -06:00
Merge pull request #4847 from zhaowenlan1779/ipc-debugger
core, citra_qt: IPC Recorder
This commit is contained in:
commit
223bfc9a2a
23 changed files with 1112 additions and 15 deletions
|
@ -90,6 +90,12 @@ add_executable(citra-qt
|
|||
debugger/graphics/graphics_tracing.h
|
||||
debugger/graphics/graphics_vertex_shader.cpp
|
||||
debugger/graphics/graphics_vertex_shader.h
|
||||
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
|
||||
|
|
64
src/citra_qt/debugger/ipc/record_dialog.cpp
Normal file
64
src/citra_qt/debugger/ipc/record_dialog.cpp
Normal file
|
@ -0,0 +1,64 @@
|
|||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include "citra_qt/debugger/ipc/record_dialog.h"
|
||||
#include "common/assert.h"
|
||||
#include "core/hle/kernel/ipc_debugger/recorder.h"
|
||||
#include "ui_record_dialog.h"
|
||||
|
||||
QString RecordDialog::FormatObject(const IPCDebugger::ObjectInfo& object) const {
|
||||
if (object.id == -1) {
|
||||
return tr("null");
|
||||
}
|
||||
|
||||
return QStringLiteral("%1 (0x%2)")
|
||||
.arg(QString::fromStdString(object.name))
|
||||
.arg(object.id, 8, 16, QLatin1Char('0'));
|
||||
}
|
||||
|
||||
QString RecordDialog::FormatCmdbuf(const std::vector<u32>& cmdbuf) const {
|
||||
QString result;
|
||||
for (std::size_t i = 0; i < cmdbuf.size(); ++i) {
|
||||
result.append(
|
||||
QStringLiteral("[%1]: 0x%2\n").arg(i).arg(cmdbuf[i], 8, 16, QLatin1Char('0')));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
RecordDialog::RecordDialog(QWidget* parent, const IPCDebugger::RequestRecord& record,
|
||||
const QString& service, const QString& function)
|
||||
: QDialog(parent), ui(std::make_unique<Ui::RecordDialog>()) {
|
||||
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->clientProcess->setText(FormatObject(record.client_process));
|
||||
ui->clientThread->setText(FormatObject(record.client_thread));
|
||||
ui->clientSession->setText(FormatObject(record.client_session));
|
||||
|
||||
ui->serverProcess->setText(FormatObject(record.server_process));
|
||||
ui->serverThread->setText(FormatObject(record.server_thread));
|
||||
ui->serverSession->setText(FormatObject(record.server_session));
|
||||
|
||||
ui->clientPort->setText(FormatObject(record.client_port));
|
||||
ui->service->setText(std::move(service));
|
||||
ui->function->setText(std::move(function));
|
||||
|
||||
cmdbufs = {record.untranslated_request_cmdbuf, record.translated_request_cmdbuf,
|
||||
record.untranslated_reply_cmdbuf, record.translated_reply_cmdbuf};
|
||||
|
||||
UpdateCmdbufDisplay();
|
||||
|
||||
connect(ui->cmdbufSelection, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||
&RecordDialog::UpdateCmdbufDisplay);
|
||||
}
|
||||
|
||||
RecordDialog::~RecordDialog() = default;
|
||||
|
||||
void RecordDialog::UpdateCmdbufDisplay() {
|
||||
int index = ui->cmdbufSelection->currentIndex();
|
||||
|
||||
ASSERT_MSG(0 <= index && index <= 3, "Index out of bound");
|
||||
ui->cmdbuf->setPlainText(FormatCmdbuf(cmdbufs[index]));
|
||||
}
|
36
src/citra_qt/debugger/ipc/record_dialog.h
Normal file
36
src/citra_qt/debugger/ipc/record_dialog.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <QDialog>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace IPCDebugger {
|
||||
struct ObjectInfo;
|
||||
struct RequestRecord;
|
||||
} // namespace IPCDebugger
|
||||
|
||||
namespace Ui {
|
||||
class RecordDialog;
|
||||
}
|
||||
|
||||
class RecordDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RecordDialog(QWidget* parent, const IPCDebugger::RequestRecord& record,
|
||||
const QString& service, const QString& function);
|
||||
~RecordDialog() override;
|
||||
|
||||
private:
|
||||
QString FormatObject(const IPCDebugger::ObjectInfo& object) const;
|
||||
QString FormatCmdbuf(const std::vector<u32>& cmdbuf) const;
|
||||
void UpdateCmdbufDisplay();
|
||||
|
||||
std::unique_ptr<Ui::RecordDialog> ui;
|
||||
std::array<std::vector<u32>, 4> cmdbufs;
|
||||
};
|
245
src/citra_qt/debugger/ipc/record_dialog.ui
Normal file
245
src/citra_qt/debugger/ipc/record_dialog.ui
Normal file
|
@ -0,0 +1,245 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>RecordDialog</class>
|
||||
<widget class="QDialog" name="RecordDialog">
|
||||
<property name="windowTitle">
|
||||
<string>View Record</string>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>500</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox">
|
||||
<property name="title">
|
||||
<string>Client</string>
|
||||
</property>
|
||||
<layout class="QFormLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Process:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="clientProcess">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Thread:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="clientThread">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Session:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="clientSession">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox">
|
||||
<property name="title">
|
||||
<string>Server</string>
|
||||
</property>
|
||||
<layout class="QFormLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Process:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="serverProcess">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Thread:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="serverThread">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Session:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="serverSession">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox">
|
||||
<property name="title">
|
||||
<string>General</string>
|
||||
</property>
|
||||
<layout class="QFormLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Client Port:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="clientPort">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Service:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="service">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Function:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="function">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox">
|
||||
<property name="title">
|
||||
<string>Command Buffer</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Select:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="cmdbufSelection">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Request Untranslated</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Request Translated</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Reply Untranslated</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Reply Translated</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="cmdbuf">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="okButton">
|
||||
<property name="text">
|
||||
<string>OK</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</ui>
|
183
src/citra_qt/debugger/ipc/recorder.cpp
Normal file
183
src/citra_qt/debugger/ipc/recorder.cpp
Normal 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();
|
||||
}
|
52
src/citra_qt/debugger/ipc/recorder.h
Normal file
52
src/citra_qt/debugger/ipc/recorder.h
Normal 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);
|
93
src/citra_qt/debugger/ipc/recorder.ui
Normal file
93
src/citra_qt/debugger/ipc/recorder.ui
Normal 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>
|
|
@ -40,6 +40,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"
|
||||
|
@ -339,6 +340,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() {
|
||||
|
|
|
@ -30,6 +30,7 @@ class GraphicsBreakPointsWidget;
|
|||
class GraphicsTracingWidget;
|
||||
class GraphicsVertexShaderWidget;
|
||||
class GRenderWindow;
|
||||
class IPCRecorderWidget;
|
||||
class LLEServiceModulesWidget;
|
||||
class MicroProfileDialog;
|
||||
class MultiplayerState;
|
||||
|
@ -254,6 +255,7 @@ private:
|
|||
GraphicsBreakPointsWidget* graphicsBreakpointsWidget;
|
||||
GraphicsVertexShaderWidget* graphicsVertexShaderWidget;
|
||||
GraphicsTracingWidget* graphicsTracingWidget;
|
||||
IPCRecorderWidget* ipcRecorderWidget;
|
||||
LLEServiceModulesWidget* lleServiceModulesWidget;
|
||||
WaitTreeWidget* waitTreeWidget;
|
||||
Updater* updater;
|
||||
|
|
|
@ -134,6 +134,8 @@ add_library(core STATIC
|
|||
hle/kernel/hle_ipc.h
|
||||
hle/kernel/ipc.cpp
|
||||
hle/kernel/ipc.h
|
||||
hle/kernel/ipc_debugger/recorder.cpp
|
||||
hle/kernel/ipc_debugger/recorder.h
|
||||
hle/kernel/kernel.cpp
|
||||
hle/kernel/kernel.h
|
||||
hle/kernel/memory.cpp
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "core/hle/kernel/event.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
#include "core/hle/kernel/ipc_debugger/recorder.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
|
||||
|
@ -107,6 +108,13 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* sr
|
|||
|
||||
std::copy_n(src_cmdbuf, untranslated_size, cmd_buf.begin());
|
||||
|
||||
const bool should_record = kernel.GetIPCRecorder().IsEnabled();
|
||||
|
||||
std::vector<u32> untranslated_cmdbuf;
|
||||
if (should_record) {
|
||||
untranslated_cmdbuf = std::vector<u32>{src_cmdbuf, src_cmdbuf + command_size};
|
||||
}
|
||||
|
||||
std::size_t i = untranslated_size;
|
||||
while (i < command_size) {
|
||||
u32 descriptor = cmd_buf[i] = src_cmdbuf[i];
|
||||
|
@ -160,6 +168,12 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* sr
|
|||
}
|
||||
}
|
||||
|
||||
if (should_record) {
|
||||
std::vector<u32> translated_cmdbuf{cmd_buf.begin(), cmd_buf.begin() + command_size};
|
||||
kernel.GetIPCRecorder().SetRequestInfo(SharedFrom(thread), std::move(untranslated_cmdbuf),
|
||||
std::move(translated_cmdbuf));
|
||||
}
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
|
@ -173,6 +187,13 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf,
|
|||
|
||||
std::copy_n(cmd_buf.begin(), untranslated_size, dst_cmdbuf);
|
||||
|
||||
const bool should_record = kernel.GetIPCRecorder().IsEnabled();
|
||||
|
||||
std::vector<u32> untranslated_cmdbuf;
|
||||
if (should_record) {
|
||||
untranslated_cmdbuf = std::vector<u32>{cmd_buf.begin(), cmd_buf.begin() + command_size};
|
||||
}
|
||||
|
||||
std::size_t i = untranslated_size;
|
||||
while (i < command_size) {
|
||||
u32 descriptor = dst_cmdbuf[i] = cmd_buf[i];
|
||||
|
@ -225,6 +246,12 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf,
|
|||
}
|
||||
}
|
||||
|
||||
if (should_record) {
|
||||
std::vector<u32> translated_cmdbuf{dst_cmdbuf, dst_cmdbuf + command_size};
|
||||
kernel.GetIPCRecorder().SetReplyInfo(SharedFrom(thread), std::move(untranslated_cmdbuf),
|
||||
std::move(translated_cmdbuf));
|
||||
}
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
|
@ -233,6 +260,12 @@ MappedBuffer& HLERequestContext::GetMappedBuffer(u32 id_from_cmdbuf) {
|
|||
return request_mapped_buffers[id_from_cmdbuf];
|
||||
}
|
||||
|
||||
void HLERequestContext::ReportUnimplemented() const {
|
||||
if (kernel.GetIPCRecorder().IsEnabled()) {
|
||||
kernel.GetIPCRecorder().SetHLEUnimplemented(SharedFrom(thread));
|
||||
}
|
||||
}
|
||||
|
||||
MappedBuffer::MappedBuffer(Memory::MemorySystem& memory, const Process& process, u32 descriptor,
|
||||
VAddr address, u32 id)
|
||||
: memory(&memory), id(id), address(address), process(&process) {
|
||||
|
|
|
@ -234,6 +234,9 @@ public:
|
|||
/// Writes data from this context back to the requesting process/thread.
|
||||
ResultCode WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process) const;
|
||||
|
||||
/// Reports an unimplemented function.
|
||||
void ReportUnimplemented() const;
|
||||
|
||||
private:
|
||||
KernelSystem& kernel;
|
||||
std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "core/hle/ipc.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/ipc.h"
|
||||
#include "core/hle/kernel/ipc_debugger/recorder.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/memory.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
|
@ -16,7 +17,8 @@
|
|||
|
||||
namespace Kernel {
|
||||
|
||||
ResultCode TranslateCommandBuffer(Memory::MemorySystem& memory, std::shared_ptr<Thread> src_thread,
|
||||
ResultCode TranslateCommandBuffer(Kernel::KernelSystem& kernel, Memory::MemorySystem& memory,
|
||||
std::shared_ptr<Thread> src_thread,
|
||||
std::shared_ptr<Thread> dst_thread, VAddr src_address,
|
||||
VAddr dst_address,
|
||||
std::vector<MappedBufferContext>& mapped_buffer_context,
|
||||
|
@ -37,6 +39,13 @@ ResultCode TranslateCommandBuffer(Memory::MemorySystem& memory, std::shared_ptr<
|
|||
std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf;
|
||||
memory.ReadBlock(*src_process, src_address, cmd_buf.data(), command_size * sizeof(u32));
|
||||
|
||||
const bool should_record = kernel.GetIPCRecorder().IsEnabled();
|
||||
|
||||
std::vector<u32> untranslated_cmdbuf;
|
||||
if (should_record) {
|
||||
untranslated_cmdbuf = std::vector<u32>{cmd_buf.begin(), cmd_buf.begin() + command_size};
|
||||
}
|
||||
|
||||
std::size_t i = untranslated_size;
|
||||
while (i < command_size) {
|
||||
u32 descriptor = cmd_buf[i];
|
||||
|
@ -218,6 +227,17 @@ ResultCode TranslateCommandBuffer(Memory::MemorySystem& memory, std::shared_ptr<
|
|||
}
|
||||
}
|
||||
|
||||
if (should_record) {
|
||||
std::vector<u32> translated_cmdbuf{cmd_buf.begin(), cmd_buf.begin() + command_size};
|
||||
if (reply) {
|
||||
kernel.GetIPCRecorder().SetReplyInfo(dst_thread, std::move(untranslated_cmdbuf),
|
||||
std::move(translated_cmdbuf));
|
||||
} else {
|
||||
kernel.GetIPCRecorder().SetRequestInfo(src_thread, std::move(untranslated_cmdbuf),
|
||||
std::move(translated_cmdbuf), dst_thread);
|
||||
}
|
||||
}
|
||||
|
||||
memory.WriteBlock(*dst_process, dst_address, cmd_buf.data(), command_size * sizeof(u32));
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
|
|
|
@ -16,6 +16,8 @@ class MemorySystem;
|
|||
|
||||
namespace Kernel {
|
||||
|
||||
class KernelSystem;
|
||||
|
||||
struct MappedBufferContext {
|
||||
IPC::MappedBufferPermissions permissions;
|
||||
u32 size;
|
||||
|
@ -27,7 +29,8 @@ struct MappedBufferContext {
|
|||
};
|
||||
|
||||
/// Performs IPC command buffer translation from one process to another.
|
||||
ResultCode TranslateCommandBuffer(Memory::MemorySystem& memory, std::shared_ptr<Thread> src_thread,
|
||||
ResultCode TranslateCommandBuffer(KernelSystem& system, Memory::MemorySystem& memory,
|
||||
std::shared_ptr<Thread> src_thread,
|
||||
std::shared_ptr<Thread> dst_thread, VAddr src_address,
|
||||
VAddr dst_address,
|
||||
std::vector<MappedBufferContext>& mapped_buffer_context,
|
||||
|
|
165
src/core/hle/kernel/ipc_debugger/recorder.cpp
Normal file
165
src/core/hle/kernel/ipc_debugger/recorder.cpp
Normal file
|
@ -0,0 +1,165 @@
|
|||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/client_session.h"
|
||||
#include "core/hle/kernel/ipc_debugger/recorder.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/server_port.h"
|
||||
#include "core/hle/kernel/server_session.h"
|
||||
#include "core/hle/kernel/session.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace IPCDebugger {
|
||||
|
||||
namespace {
|
||||
ObjectInfo GetObjectInfo(const Kernel::Object* object) {
|
||||
if (object == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return {object->GetTypeName(), object->GetName(), static_cast<int>(object->GetObjectId())};
|
||||
}
|
||||
|
||||
ObjectInfo GetObjectInfo(const Kernel::Thread* thread) {
|
||||
if (thread == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return {thread->GetTypeName(), thread->GetName(), static_cast<int>(thread->GetThreadId())};
|
||||
}
|
||||
|
||||
ObjectInfo GetObjectInfo(const Kernel::Process* process) {
|
||||
if (process == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return {process->GetTypeName(), process->GetName(), static_cast<int>(process->process_id)};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Recorder::Recorder() = default;
|
||||
Recorder::~Recorder() = default;
|
||||
|
||||
bool Recorder::IsEnabled() const {
|
||||
return enabled.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void Recorder::RegisterRequest(const std::shared_ptr<Kernel::ClientSession>& client_session,
|
||||
const std::shared_ptr<Kernel::Thread>& client_thread) {
|
||||
const u32 thread_id = client_thread->GetThreadId();
|
||||
|
||||
RequestRecord record = {/* id */ ++record_count,
|
||||
/* status */ RequestStatus::Sent,
|
||||
/* client_process */ GetObjectInfo(client_thread->owner_process),
|
||||
/* client_thread */ GetObjectInfo(client_thread.get()),
|
||||
/* client_session */ GetObjectInfo(client_session.get()),
|
||||
/* client_port */ GetObjectInfo(client_session->parent->port.get()),
|
||||
/* server_process */ {},
|
||||
/* server_thread */ {},
|
||||
/* server_session */ GetObjectInfo(client_session->parent->server)};
|
||||
record_map.insert_or_assign(thread_id, std::make_unique<RequestRecord>(record));
|
||||
client_session_map.insert_or_assign(thread_id, client_session);
|
||||
|
||||
InvokeCallbacks(record);
|
||||
}
|
||||
|
||||
void Recorder::SetRequestInfo(const std::shared_ptr<Kernel::Thread>& client_thread,
|
||||
std::vector<u32> untranslated_cmdbuf,
|
||||
std::vector<u32> translated_cmdbuf,
|
||||
const std::shared_ptr<Kernel::Thread>& server_thread) {
|
||||
const u32 thread_id = client_thread->GetThreadId();
|
||||
if (!record_map.count(thread_id)) {
|
||||
// This is possible when the recorder is enabled after application started
|
||||
LOG_ERROR(Kernel, "No request is assoicated with the thread");
|
||||
return;
|
||||
}
|
||||
|
||||
auto& record = *record_map[thread_id];
|
||||
record.status = RequestStatus::Handling;
|
||||
record.untranslated_request_cmdbuf = std::move(untranslated_cmdbuf);
|
||||
record.translated_request_cmdbuf = std::move(translated_cmdbuf);
|
||||
|
||||
if (server_thread) {
|
||||
record.server_process = GetObjectInfo(server_thread->owner_process);
|
||||
record.server_thread = GetObjectInfo(server_thread.get());
|
||||
} else {
|
||||
record.is_hle = true;
|
||||
}
|
||||
|
||||
// Function name
|
||||
ASSERT_MSG(client_session_map.count(thread_id), "Client session is missing");
|
||||
const auto& client_session = client_session_map[thread_id];
|
||||
if (client_session->parent->port &&
|
||||
client_session->parent->port->GetServerPort()->hle_handler) {
|
||||
|
||||
record.function_name = std::dynamic_pointer_cast<Service::ServiceFrameworkBase>(
|
||||
client_session->parent->port->GetServerPort()->hle_handler)
|
||||
->GetFunctionName(record.untranslated_request_cmdbuf[0]);
|
||||
}
|
||||
client_session_map.erase(thread_id);
|
||||
|
||||
InvokeCallbacks(record);
|
||||
}
|
||||
|
||||
void Recorder::SetReplyInfo(const std::shared_ptr<Kernel::Thread>& client_thread,
|
||||
std::vector<u32> untranslated_cmdbuf,
|
||||
std::vector<u32> translated_cmdbuf) {
|
||||
const u32 thread_id = client_thread->GetThreadId();
|
||||
if (!record_map.count(thread_id)) {
|
||||
// This is possible when the recorder is enabled after application started
|
||||
LOG_ERROR(Kernel, "No request is assoicated with the thread");
|
||||
return;
|
||||
}
|
||||
|
||||
auto& record = *record_map[thread_id];
|
||||
if (record.status != RequestStatus::HLEUnimplemented) {
|
||||
record.status = RequestStatus::Handled;
|
||||
}
|
||||
|
||||
record.untranslated_reply_cmdbuf = std::move(untranslated_cmdbuf);
|
||||
record.translated_reply_cmdbuf = std::move(translated_cmdbuf);
|
||||
InvokeCallbacks(record);
|
||||
|
||||
record_map.erase(thread_id);
|
||||
}
|
||||
|
||||
void Recorder::SetHLEUnimplemented(const std::shared_ptr<Kernel::Thread>& client_thread) {
|
||||
const u32 thread_id = client_thread->GetThreadId();
|
||||
if (!record_map.count(thread_id)) {
|
||||
// This is possible when the recorder is enabled after application started
|
||||
LOG_ERROR(Kernel, "No request is assoicated with the thread");
|
||||
return;
|
||||
}
|
||||
|
||||
auto& record = *record_map[thread_id];
|
||||
record.status = RequestStatus::HLEUnimplemented;
|
||||
}
|
||||
|
||||
CallbackHandle Recorder::BindCallback(CallbackType callback) {
|
||||
std::unique_lock lock(callback_mutex);
|
||||
CallbackHandle handle = std::make_shared<CallbackType>(callback);
|
||||
callbacks.emplace(handle);
|
||||
return handle;
|
||||
}
|
||||
|
||||
void Recorder::UnbindCallback(const CallbackHandle& handle) {
|
||||
std::unique_lock lock(callback_mutex);
|
||||
callbacks.erase(handle);
|
||||
}
|
||||
|
||||
void Recorder::InvokeCallbacks(const RequestRecord& request) {
|
||||
{
|
||||
std::shared_lock lock(callback_mutex);
|
||||
for (const auto& iter : callbacks) {
|
||||
(*iter)(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Recorder::SetEnabled(bool enabled_) {
|
||||
enabled.store(enabled_, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
} // namespace IPCDebugger
|
129
src/core/hle/kernel/ipc_debugger/recorder.h
Normal file
129
src/core/hle/kernel/ipc_debugger/recorder.h
Normal file
|
@ -0,0 +1,129 @@
|
|||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Kernel {
|
||||
class ClientSession;
|
||||
class Thread;
|
||||
} // namespace Kernel
|
||||
|
||||
namespace IPCDebugger {
|
||||
|
||||
/**
|
||||
* Record of a kernel object, for debugging purposes.
|
||||
*/
|
||||
struct ObjectInfo {
|
||||
std::string type;
|
||||
std::string name;
|
||||
int id = -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Status of a request.
|
||||
*/
|
||||
enum class RequestStatus {
|
||||
Invalid, ///< Invalid status
|
||||
Sent, ///< The request is sent to the kernel and is waiting to be handled
|
||||
Handling, ///< The request is being handled
|
||||
Handled, ///< The request is handled with reply sent
|
||||
HLEUnimplemented, ///< The request is unimplemented by HLE, and unhandled
|
||||
};
|
||||
|
||||
/**
|
||||
* Record of an IPC request.
|
||||
*/
|
||||
struct RequestRecord {
|
||||
int id;
|
||||
RequestStatus status = RequestStatus::Invalid;
|
||||
ObjectInfo client_process;
|
||||
ObjectInfo client_thread;
|
||||
ObjectInfo client_session;
|
||||
ObjectInfo client_port; // Not available for portless
|
||||
ObjectInfo server_process; // Only available for LLE requests
|
||||
ObjectInfo server_thread; // Only available for LLE requests
|
||||
ObjectInfo server_session;
|
||||
std::string function_name; // Not available for LLE or portless
|
||||
bool is_hle = false;
|
||||
// Request info is only available when status is not `Invalid` or `Sent`
|
||||
std::vector<u32> untranslated_request_cmdbuf;
|
||||
std::vector<u32> translated_request_cmdbuf;
|
||||
// Reply info is only available when status is `Handled`
|
||||
std::vector<u32> untranslated_reply_cmdbuf;
|
||||
std::vector<u32> translated_reply_cmdbuf;
|
||||
};
|
||||
|
||||
using CallbackType = std::function<void(const RequestRecord&)>;
|
||||
using CallbackHandle = std::shared_ptr<CallbackType>;
|
||||
|
||||
class Recorder {
|
||||
public:
|
||||
explicit Recorder();
|
||||
~Recorder();
|
||||
|
||||
/**
|
||||
* Returns whether the recorder is enabled.
|
||||
*/
|
||||
bool IsEnabled() const;
|
||||
|
||||
/**
|
||||
* Registers a request into the recorder. The request is then assoicated with the client thread.
|
||||
*/
|
||||
void RegisterRequest(const std::shared_ptr<Kernel::ClientSession>& client_session,
|
||||
const std::shared_ptr<Kernel::Thread>& client_thread);
|
||||
|
||||
/**
|
||||
* Sets the request information of the request record associated with the client thread.
|
||||
* When the server thread is empty, the request will be considered HLE.
|
||||
*/
|
||||
void SetRequestInfo(const std::shared_ptr<Kernel::Thread>& client_thread,
|
||||
std::vector<u32> untranslated_cmdbuf, std::vector<u32> translated_cmdbuf,
|
||||
const std::shared_ptr<Kernel::Thread>& server_thread = {});
|
||||
|
||||
/**
|
||||
* Sets the reply information of the request record assoicated with the client thread.
|
||||
* The request is then unlinked from the client thread.
|
||||
*/
|
||||
void SetReplyInfo(const std::shared_ptr<Kernel::Thread>& client_thread,
|
||||
std::vector<u32> untranslated_cmdbuf, std::vector<u32> translated_cmdbuf);
|
||||
|
||||
/**
|
||||
* Set the status of a record to HLEUnimplemented.
|
||||
*/
|
||||
void SetHLEUnimplemented(const std::shared_ptr<Kernel::Thread>& client_thread);
|
||||
|
||||
/**
|
||||
* Set the status of the debugger (enabled/disabled).
|
||||
*/
|
||||
void SetEnabled(bool enabled);
|
||||
|
||||
CallbackHandle BindCallback(CallbackType callback);
|
||||
void UnbindCallback(const CallbackHandle& handle);
|
||||
|
||||
private:
|
||||
void InvokeCallbacks(const RequestRecord& request);
|
||||
|
||||
std::unordered_map<u32, std::unique_ptr<RequestRecord>> record_map;
|
||||
int record_count{};
|
||||
|
||||
// Temporary client session map for function name handling
|
||||
std::unordered_map<u32, std::shared_ptr<Kernel::ClientSession>> client_session_map;
|
||||
|
||||
std::atomic_bool enabled{false};
|
||||
|
||||
std::set<CallbackHandle> callbacks;
|
||||
mutable std::shared_mutex callback_mutex;
|
||||
};
|
||||
|
||||
} // namespace IPCDebugger
|
|
@ -5,6 +5,7 @@
|
|||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/config_mem.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/ipc_debugger/recorder.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/memory.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
|
@ -25,6 +26,7 @@ KernelSystem::KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing,
|
|||
resource_limits = std::make_unique<ResourceLimitList>(*this);
|
||||
thread_manager = std::make_unique<ThreadManager>(*this);
|
||||
timer_manager = std::make_unique<TimerManager>(timing);
|
||||
ipc_recorder = std::make_unique<IPCDebugger::Recorder>();
|
||||
}
|
||||
|
||||
/// Shutdown the kernel
|
||||
|
@ -87,6 +89,14 @@ const SharedPage::Handler& KernelSystem::GetSharedPageHandler() const {
|
|||
return *shared_page_handler;
|
||||
}
|
||||
|
||||
IPCDebugger::Recorder& KernelSystem::GetIPCRecorder() {
|
||||
return *ipc_recorder;
|
||||
}
|
||||
|
||||
const IPCDebugger::Recorder& KernelSystem::GetIPCRecorder() const {
|
||||
return *ipc_recorder;
|
||||
}
|
||||
|
||||
void KernelSystem::AddNamedPort(std::string name, std::shared_ptr<ClientPort> port) {
|
||||
named_ports.emplace(std::move(name), std::move(port));
|
||||
}
|
||||
|
|
|
@ -32,6 +32,10 @@ namespace Core {
|
|||
class Timing;
|
||||
}
|
||||
|
||||
namespace IPCDebugger {
|
||||
class Recorder;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class AddressArbiter;
|
||||
|
@ -222,6 +226,9 @@ public:
|
|||
SharedPage::Handler& GetSharedPageHandler();
|
||||
const SharedPage::Handler& GetSharedPageHandler() const;
|
||||
|
||||
IPCDebugger::Recorder& GetIPCRecorder();
|
||||
const IPCDebugger::Recorder& GetIPCRecorder() const;
|
||||
|
||||
MemoryRegionInfo* GetMemoryRegion(MemoryRegion region);
|
||||
|
||||
void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping);
|
||||
|
@ -274,6 +281,8 @@ private:
|
|||
|
||||
std::unique_ptr<ConfigMem::Handler> config_mem_handler;
|
||||
std::unique_ptr<SharedPage::Handler> shared_page_handler;
|
||||
|
||||
std::unique_ptr<IPCDebugger::Recorder> ipc_recorder;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "core/hle/kernel/event.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/ipc.h"
|
||||
#include "core/hle/kernel/ipc_debugger/recorder.h"
|
||||
#include "core/hle/kernel/memory.h"
|
||||
#include "core/hle/kernel/mutex.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
|
@ -387,7 +388,13 @@ ResultCode SVC::SendSyncRequest(Handle handle) {
|
|||
|
||||
system.PrepareReschedule();
|
||||
|
||||
return session->SendSyncRequest(SharedFrom(kernel.GetThreadManager().GetCurrentThread()));
|
||||
auto thread = SharedFrom(kernel.GetThreadManager().GetCurrentThread());
|
||||
|
||||
if (kernel.GetIPCRecorder().IsEnabled()) {
|
||||
kernel.GetIPCRecorder().RegisterRequest(session, thread);
|
||||
}
|
||||
|
||||
return session->SendSyncRequest(thread);
|
||||
}
|
||||
|
||||
/// Close a handle
|
||||
|
@ -593,7 +600,7 @@ ResultCode SVC::WaitSynchronizationN(s32* out, VAddr handles_address, s32 handle
|
|||
}
|
||||
}
|
||||
|
||||
static ResultCode ReceiveIPCRequest(Memory::MemorySystem& memory,
|
||||
static ResultCode ReceiveIPCRequest(Kernel::KernelSystem& kernel, Memory::MemorySystem& memory,
|
||||
std::shared_ptr<ServerSession> server_session,
|
||||
std::shared_ptr<Thread> thread) {
|
||||
if (server_session->parent->client == nullptr) {
|
||||
|
@ -603,9 +610,9 @@ static ResultCode ReceiveIPCRequest(Memory::MemorySystem& memory,
|
|||
VAddr target_address = thread->GetCommandBufferAddress();
|
||||
VAddr source_address = server_session->currently_handling->GetCommandBufferAddress();
|
||||
|
||||
ResultCode translation_result =
|
||||
TranslateCommandBuffer(memory, server_session->currently_handling, thread, source_address,
|
||||
target_address, server_session->mapped_buffer_context, false);
|
||||
ResultCode translation_result = TranslateCommandBuffer(
|
||||
kernel, memory, server_session->currently_handling, thread, source_address, target_address,
|
||||
server_session->mapped_buffer_context, false);
|
||||
|
||||
// If a translation error occurred, immediately resume the client thread.
|
||||
if (translation_result.IsError()) {
|
||||
|
@ -670,9 +677,9 @@ ResultCode SVC::ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_co
|
|||
VAddr source_address = thread->GetCommandBufferAddress();
|
||||
VAddr target_address = request_thread->GetCommandBufferAddress();
|
||||
|
||||
ResultCode translation_result =
|
||||
TranslateCommandBuffer(memory, SharedFrom(thread), request_thread, source_address,
|
||||
target_address, session->mapped_buffer_context, true);
|
||||
ResultCode translation_result = TranslateCommandBuffer(
|
||||
kernel, memory, SharedFrom(thread), request_thread, source_address, target_address,
|
||||
session->mapped_buffer_context, true);
|
||||
|
||||
// Note: The real kernel seems to always panic if the Server->Client buffer translation
|
||||
// fails for whatever reason.
|
||||
|
@ -707,7 +714,7 @@ ResultCode SVC::ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_co
|
|||
return RESULT_SUCCESS;
|
||||
|
||||
auto server_session = static_cast<ServerSession*>(object);
|
||||
return ReceiveIPCRequest(memory, SharedFrom(server_session), SharedFrom(thread));
|
||||
return ReceiveIPCRequest(kernel, memory, SharedFrom(server_session), SharedFrom(thread));
|
||||
}
|
||||
|
||||
// No objects were ready to be acquired, prepare to suspend the thread.
|
||||
|
@ -723,9 +730,9 @@ ResultCode SVC::ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_co
|
|||
|
||||
thread->wait_objects = std::move(objects);
|
||||
|
||||
thread->wakeup_callback = [& memory = this->memory](ThreadWakeupReason reason,
|
||||
std::shared_ptr<Thread> thread,
|
||||
std::shared_ptr<WaitObject> object) {
|
||||
thread->wakeup_callback = [& kernel = this->kernel, &memory = this->memory](
|
||||
ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
|
||||
std::shared_ptr<WaitObject> object) {
|
||||
ASSERT(thread->status == ThreadStatus::WaitSynchAny);
|
||||
ASSERT(reason == ThreadWakeupReason::Signal);
|
||||
|
||||
|
@ -733,7 +740,7 @@ ResultCode SVC::ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_co
|
|||
|
||||
if (object->GetHandleType() == HandleType::ServerSession) {
|
||||
auto server_session = DynamicObjectCast<ServerSession>(object);
|
||||
result = ReceiveIPCRequest(memory, server_session, thread);
|
||||
result = ReceiveIPCRequest(kernel, memory, server_session, thread);
|
||||
}
|
||||
|
||||
thread->SetWaitSynchronizationResult(result);
|
||||
|
|
|
@ -171,6 +171,7 @@ void ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& context)
|
|||
auto itr = handlers.find(header_code);
|
||||
const FunctionInfoBase* info = itr == handlers.end() ? nullptr : &itr->second;
|
||||
if (info == nullptr || info->handler_callback == nullptr) {
|
||||
context.ReportUnimplemented();
|
||||
return ReportUnimplementedFunction(context.CommandBuffer(), info);
|
||||
}
|
||||
|
||||
|
@ -179,6 +180,14 @@ void ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& context)
|
|||
handler_invoker(this, info->handler_callback, context);
|
||||
}
|
||||
|
||||
std::string ServiceFrameworkBase::GetFunctionName(u32 header) const {
|
||||
if (!handlers.count(header)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return handlers.at(header).name;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Module interface
|
||||
|
||||
|
|
|
@ -64,6 +64,9 @@ public:
|
|||
|
||||
void HandleSyncRequest(Kernel::HLERequestContext& context) override;
|
||||
|
||||
/// Retrieves name of a function based on the header code. For IPC Recorder.
|
||||
std::string GetFunctionName(u32 header) const;
|
||||
|
||||
protected:
|
||||
/// Member-function pointer type of SyncRequest handlers.
|
||||
template <typename Self>
|
||||
|
|
|
@ -42,6 +42,7 @@ ResultVal<std::shared_ptr<Kernel::ServerPort>> ServiceManager::RegisterService(
|
|||
|
||||
auto [server_port, client_port] = system.Kernel().CreatePortPair(max_sessions, name);
|
||||
|
||||
registered_services_inverse.emplace(client_port->GetObjectId(), name);
|
||||
registered_services.emplace(std::move(name), std::move(client_port));
|
||||
return MakeResult(std::move(server_port));
|
||||
}
|
||||
|
@ -65,4 +66,12 @@ ResultVal<std::shared_ptr<Kernel::ClientSession>> ServiceManager::ConnectToServi
|
|||
return client_port->Connect();
|
||||
}
|
||||
|
||||
std::string ServiceManager::GetServiceNameByPortId(u32 port) const {
|
||||
if (registered_services_inverse.count(port)) {
|
||||
return registered_services_inverse.at(port);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace Service::SM
|
||||
|
|
|
@ -51,6 +51,8 @@ public:
|
|||
unsigned int max_sessions);
|
||||
ResultVal<std::shared_ptr<Kernel::ClientPort>> GetServicePort(const std::string& name);
|
||||
ResultVal<std::shared_ptr<Kernel::ClientSession>> ConnectToService(const std::string& name);
|
||||
// For IPC Recorder
|
||||
std::string GetServiceNameByPortId(u32 port) const;
|
||||
|
||||
template <typename T>
|
||||
std::shared_ptr<T> GetService(const std::string& service_name) const {
|
||||
|
@ -74,6 +76,10 @@ private:
|
|||
|
||||
/// Map of registered services, retrieved using GetServicePort or ConnectToService.
|
||||
std::unordered_map<std::string, std::shared_ptr<Kernel::ClientPort>> registered_services;
|
||||
|
||||
// For IPC Recorder
|
||||
/// client port Object id -> service name
|
||||
std::unordered_map<u32, std::string> registered_services_inverse;
|
||||
};
|
||||
|
||||
} // namespace Service::SM
|
||||
|
|
Loading…
Reference in a new issue