Merge pull request #4847 from zhaowenlan1779/ipc-debugger

core, citra_qt: IPC Recorder
This commit is contained in:
James Rowe 2019-09-21 00:04:07 -06:00 committed by GitHub
commit 223bfc9a2a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 1112 additions and 15 deletions

View file

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

View 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]));
}

View 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;
};

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

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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

View 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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