mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00: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_tracing.h | ||||||
|     debugger/graphics/graphics_vertex_shader.cpp |     debugger/graphics/graphics_vertex_shader.cpp | ||||||
|     debugger/graphics/graphics_vertex_shader.h |     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.cpp | ||||||
|     debugger/lle_service_modules.h |     debugger/lle_service_modules.h | ||||||
|     debugger/profiler.cpp |     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_surface.h" | ||||||
| #include "citra_qt/debugger/graphics/graphics_tracing.h" | #include "citra_qt/debugger/graphics/graphics_tracing.h" | ||||||
| #include "citra_qt/debugger/graphics/graphics_vertex_shader.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/lle_service_modules.h" | ||||||
| #include "citra_qt/debugger/profiler.h" | #include "citra_qt/debugger/profiler.h" | ||||||
| #include "citra_qt/debugger/registers.h" | #include "citra_qt/debugger/registers.h" | ||||||
|  | @ -339,6 +340,13 @@ void GMainWindow::InitializeDebugWidgets() { | ||||||
|             [this] { lleServiceModulesWidget->setDisabled(true); }); |             [this] { lleServiceModulesWidget->setDisabled(true); }); | ||||||
|     connect(this, &GMainWindow::EmulationStopping, waitTreeWidget, |     connect(this, &GMainWindow::EmulationStopping, waitTreeWidget, | ||||||
|             [this] { lleServiceModulesWidget->setDisabled(false); }); |             [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() { | void GMainWindow::InitializeRecentFileMenuActions() { | ||||||
|  |  | ||||||
|  | @ -30,6 +30,7 @@ class GraphicsBreakPointsWidget; | ||||||
| class GraphicsTracingWidget; | class GraphicsTracingWidget; | ||||||
| class GraphicsVertexShaderWidget; | class GraphicsVertexShaderWidget; | ||||||
| class GRenderWindow; | class GRenderWindow; | ||||||
|  | class IPCRecorderWidget; | ||||||
| class LLEServiceModulesWidget; | class LLEServiceModulesWidget; | ||||||
| class MicroProfileDialog; | class MicroProfileDialog; | ||||||
| class MultiplayerState; | class MultiplayerState; | ||||||
|  | @ -254,6 +255,7 @@ private: | ||||||
|     GraphicsBreakPointsWidget* graphicsBreakpointsWidget; |     GraphicsBreakPointsWidget* graphicsBreakpointsWidget; | ||||||
|     GraphicsVertexShaderWidget* graphicsVertexShaderWidget; |     GraphicsVertexShaderWidget* graphicsVertexShaderWidget; | ||||||
|     GraphicsTracingWidget* graphicsTracingWidget; |     GraphicsTracingWidget* graphicsTracingWidget; | ||||||
|  |     IPCRecorderWidget* ipcRecorderWidget; | ||||||
|     LLEServiceModulesWidget* lleServiceModulesWidget; |     LLEServiceModulesWidget* lleServiceModulesWidget; | ||||||
|     WaitTreeWidget* waitTreeWidget; |     WaitTreeWidget* waitTreeWidget; | ||||||
|     Updater* updater; |     Updater* updater; | ||||||
|  |  | ||||||
|  | @ -134,6 +134,8 @@ add_library(core STATIC | ||||||
|     hle/kernel/hle_ipc.h |     hle/kernel/hle_ipc.h | ||||||
|     hle/kernel/ipc.cpp |     hle/kernel/ipc.cpp | ||||||
|     hle/kernel/ipc.h |     hle/kernel/ipc.h | ||||||
|  |     hle/kernel/ipc_debugger/recorder.cpp | ||||||
|  |     hle/kernel/ipc_debugger/recorder.h | ||||||
|     hle/kernel/kernel.cpp |     hle/kernel/kernel.cpp | ||||||
|     hle/kernel/kernel.h |     hle/kernel/kernel.h | ||||||
|     hle/kernel/memory.cpp |     hle/kernel/memory.cpp | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ | ||||||
| #include "core/hle/kernel/event.h" | #include "core/hle/kernel/event.h" | ||||||
| #include "core/hle/kernel/handle_table.h" | #include "core/hle/kernel/handle_table.h" | ||||||
| #include "core/hle/kernel/hle_ipc.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/kernel.h" | ||||||
| #include "core/hle/kernel/process.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()); |     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; |     std::size_t i = untranslated_size; | ||||||
|     while (i < command_size) { |     while (i < command_size) { | ||||||
|         u32 descriptor = cmd_buf[i] = src_cmdbuf[i]; |         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; |     return RESULT_SUCCESS; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -173,6 +187,13 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, | ||||||
| 
 | 
 | ||||||
|     std::copy_n(cmd_buf.begin(), untranslated_size, 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; |     std::size_t i = untranslated_size; | ||||||
|     while (i < command_size) { |     while (i < command_size) { | ||||||
|         u32 descriptor = dst_cmdbuf[i] = cmd_buf[i]; |         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; |     return RESULT_SUCCESS; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -233,6 +260,12 @@ MappedBuffer& HLERequestContext::GetMappedBuffer(u32 id_from_cmdbuf) { | ||||||
|     return request_mapped_buffers[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, | MappedBuffer::MappedBuffer(Memory::MemorySystem& memory, const Process& process, u32 descriptor, | ||||||
|                            VAddr address, u32 id) |                            VAddr address, u32 id) | ||||||
|     : memory(&memory), id(id), address(address), process(&process) { |     : memory(&memory), id(id), address(address), process(&process) { | ||||||
|  |  | ||||||
|  | @ -234,6 +234,9 @@ public: | ||||||
|     /// Writes data from this context back to the requesting process/thread.
 |     /// Writes data from this context back to the requesting process/thread.
 | ||||||
|     ResultCode WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process) const; |     ResultCode WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process) const; | ||||||
| 
 | 
 | ||||||
|  |     /// Reports an unimplemented function.
 | ||||||
|  |     void ReportUnimplemented() const; | ||||||
|  | 
 | ||||||
| private: | private: | ||||||
|     KernelSystem& kernel; |     KernelSystem& kernel; | ||||||
|     std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf; |     std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf; | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ | ||||||
| #include "core/hle/ipc.h" | #include "core/hle/ipc.h" | ||||||
| #include "core/hle/kernel/handle_table.h" | #include "core/hle/kernel/handle_table.h" | ||||||
| #include "core/hle/kernel/ipc.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/kernel.h" | ||||||
| #include "core/hle/kernel/memory.h" | #include "core/hle/kernel/memory.h" | ||||||
| #include "core/hle/kernel/process.h" | #include "core/hle/kernel/process.h" | ||||||
|  | @ -16,7 +17,8 @@ | ||||||
| 
 | 
 | ||||||
| namespace Kernel { | 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, |                                   std::shared_ptr<Thread> dst_thread, VAddr src_address, | ||||||
|                                   VAddr dst_address, |                                   VAddr dst_address, | ||||||
|                                   std::vector<MappedBufferContext>& mapped_buffer_context, |                                   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; |     std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf; | ||||||
|     memory.ReadBlock(*src_process, src_address, cmd_buf.data(), command_size * sizeof(u32)); |     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; |     std::size_t i = untranslated_size; | ||||||
|     while (i < command_size) { |     while (i < command_size) { | ||||||
|         u32 descriptor = cmd_buf[i]; |         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)); |     memory.WriteBlock(*dst_process, dst_address, cmd_buf.data(), command_size * sizeof(u32)); | ||||||
| 
 | 
 | ||||||
|     return RESULT_SUCCESS; |     return RESULT_SUCCESS; | ||||||
|  |  | ||||||
|  | @ -16,6 +16,8 @@ class MemorySystem; | ||||||
| 
 | 
 | ||||||
| namespace Kernel { | namespace Kernel { | ||||||
| 
 | 
 | ||||||
|  | class KernelSystem; | ||||||
|  | 
 | ||||||
| struct MappedBufferContext { | struct MappedBufferContext { | ||||||
|     IPC::MappedBufferPermissions permissions; |     IPC::MappedBufferPermissions permissions; | ||||||
|     u32 size; |     u32 size; | ||||||
|  | @ -27,7 +29,8 @@ struct MappedBufferContext { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /// Performs IPC command buffer translation from one process to another.
 | /// 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, |                                   std::shared_ptr<Thread> dst_thread, VAddr src_address, | ||||||
|                                   VAddr dst_address, |                                   VAddr dst_address, | ||||||
|                                   std::vector<MappedBufferContext>& mapped_buffer_context, |                                   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/client_port.h" | ||||||
| #include "core/hle/kernel/config_mem.h" | #include "core/hle/kernel/config_mem.h" | ||||||
| #include "core/hle/kernel/handle_table.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/kernel.h" | ||||||
| #include "core/hle/kernel/memory.h" | #include "core/hle/kernel/memory.h" | ||||||
| #include "core/hle/kernel/process.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); |     resource_limits = std::make_unique<ResourceLimitList>(*this); | ||||||
|     thread_manager = std::make_unique<ThreadManager>(*this); |     thread_manager = std::make_unique<ThreadManager>(*this); | ||||||
|     timer_manager = std::make_unique<TimerManager>(timing); |     timer_manager = std::make_unique<TimerManager>(timing); | ||||||
|  |     ipc_recorder = std::make_unique<IPCDebugger::Recorder>(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Shutdown the kernel
 | /// Shutdown the kernel
 | ||||||
|  | @ -87,6 +89,14 @@ const SharedPage::Handler& KernelSystem::GetSharedPageHandler() const { | ||||||
|     return *shared_page_handler; |     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) { | void KernelSystem::AddNamedPort(std::string name, std::shared_ptr<ClientPort> port) { | ||||||
|     named_ports.emplace(std::move(name), std::move(port)); |     named_ports.emplace(std::move(name), std::move(port)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -32,6 +32,10 @@ namespace Core { | ||||||
| class Timing; | class Timing; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | namespace IPCDebugger { | ||||||
|  | class Recorder; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| namespace Kernel { | namespace Kernel { | ||||||
| 
 | 
 | ||||||
| class AddressArbiter; | class AddressArbiter; | ||||||
|  | @ -222,6 +226,9 @@ public: | ||||||
|     SharedPage::Handler& GetSharedPageHandler(); |     SharedPage::Handler& GetSharedPageHandler(); | ||||||
|     const SharedPage::Handler& GetSharedPageHandler() const; |     const SharedPage::Handler& GetSharedPageHandler() const; | ||||||
| 
 | 
 | ||||||
|  |     IPCDebugger::Recorder& GetIPCRecorder(); | ||||||
|  |     const IPCDebugger::Recorder& GetIPCRecorder() const; | ||||||
|  | 
 | ||||||
|     MemoryRegionInfo* GetMemoryRegion(MemoryRegion region); |     MemoryRegionInfo* GetMemoryRegion(MemoryRegion region); | ||||||
| 
 | 
 | ||||||
|     void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping); |     void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping); | ||||||
|  | @ -274,6 +281,8 @@ private: | ||||||
| 
 | 
 | ||||||
|     std::unique_ptr<ConfigMem::Handler> config_mem_handler; |     std::unique_ptr<ConfigMem::Handler> config_mem_handler; | ||||||
|     std::unique_ptr<SharedPage::Handler> shared_page_handler; |     std::unique_ptr<SharedPage::Handler> shared_page_handler; | ||||||
|  | 
 | ||||||
|  |     std::unique_ptr<IPCDebugger::Recorder> ipc_recorder; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace Kernel
 | } // namespace Kernel
 | ||||||
|  |  | ||||||
|  | @ -19,6 +19,7 @@ | ||||||
| #include "core/hle/kernel/event.h" | #include "core/hle/kernel/event.h" | ||||||
| #include "core/hle/kernel/handle_table.h" | #include "core/hle/kernel/handle_table.h" | ||||||
| #include "core/hle/kernel/ipc.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/memory.h" | ||||||
| #include "core/hle/kernel/mutex.h" | #include "core/hle/kernel/mutex.h" | ||||||
| #include "core/hle/kernel/process.h" | #include "core/hle/kernel/process.h" | ||||||
|  | @ -387,7 +388,13 @@ ResultCode SVC::SendSyncRequest(Handle handle) { | ||||||
| 
 | 
 | ||||||
|     system.PrepareReschedule(); |     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
 | /// 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<ServerSession> server_session, | ||||||
|                                     std::shared_ptr<Thread> thread) { |                                     std::shared_ptr<Thread> thread) { | ||||||
|     if (server_session->parent->client == nullptr) { |     if (server_session->parent->client == nullptr) { | ||||||
|  | @ -603,9 +610,9 @@ static ResultCode ReceiveIPCRequest(Memory::MemorySystem& memory, | ||||||
|     VAddr target_address = thread->GetCommandBufferAddress(); |     VAddr target_address = thread->GetCommandBufferAddress(); | ||||||
|     VAddr source_address = server_session->currently_handling->GetCommandBufferAddress(); |     VAddr source_address = server_session->currently_handling->GetCommandBufferAddress(); | ||||||
| 
 | 
 | ||||||
|     ResultCode translation_result = |     ResultCode translation_result = TranslateCommandBuffer( | ||||||
|         TranslateCommandBuffer(memory, server_session->currently_handling, thread, source_address, |         kernel, memory, server_session->currently_handling, thread, source_address, target_address, | ||||||
|                                target_address, server_session->mapped_buffer_context, false); |         server_session->mapped_buffer_context, false); | ||||||
| 
 | 
 | ||||||
|     // If a translation error occurred, immediately resume the client thread.
 |     // If a translation error occurred, immediately resume the client thread.
 | ||||||
|     if (translation_result.IsError()) { |     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 source_address = thread->GetCommandBufferAddress(); | ||||||
|         VAddr target_address = request_thread->GetCommandBufferAddress(); |         VAddr target_address = request_thread->GetCommandBufferAddress(); | ||||||
| 
 | 
 | ||||||
|         ResultCode translation_result = |         ResultCode translation_result = TranslateCommandBuffer( | ||||||
|             TranslateCommandBuffer(memory, SharedFrom(thread), request_thread, source_address, |             kernel, memory, SharedFrom(thread), request_thread, source_address, target_address, | ||||||
|                                    target_address, session->mapped_buffer_context, true); |             session->mapped_buffer_context, true); | ||||||
| 
 | 
 | ||||||
|         // Note: The real kernel seems to always panic if the Server->Client buffer translation
 |         // Note: The real kernel seems to always panic if the Server->Client buffer translation
 | ||||||
|         // fails for whatever reason.
 |         // fails for whatever reason.
 | ||||||
|  | @ -707,7 +714,7 @@ ResultCode SVC::ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_co | ||||||
|             return RESULT_SUCCESS; |             return RESULT_SUCCESS; | ||||||
| 
 | 
 | ||||||
|         auto server_session = static_cast<ServerSession*>(object); |         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.
 |     // 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->wait_objects = std::move(objects); | ||||||
| 
 | 
 | ||||||
|     thread->wakeup_callback = [& memory = this->memory](ThreadWakeupReason reason, |     thread->wakeup_callback = [& kernel = this->kernel, &memory = this->memory]( | ||||||
|                                                         std::shared_ptr<Thread> thread, |                                   ThreadWakeupReason reason, std::shared_ptr<Thread> thread, | ||||||
|                                                         std::shared_ptr<WaitObject> object) { |                                   std::shared_ptr<WaitObject> object) { | ||||||
|         ASSERT(thread->status == ThreadStatus::WaitSynchAny); |         ASSERT(thread->status == ThreadStatus::WaitSynchAny); | ||||||
|         ASSERT(reason == ThreadWakeupReason::Signal); |         ASSERT(reason == ThreadWakeupReason::Signal); | ||||||
| 
 | 
 | ||||||
|  | @ -733,7 +740,7 @@ ResultCode SVC::ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_co | ||||||
| 
 | 
 | ||||||
|         if (object->GetHandleType() == HandleType::ServerSession) { |         if (object->GetHandleType() == HandleType::ServerSession) { | ||||||
|             auto server_session = DynamicObjectCast<ServerSession>(object); |             auto server_session = DynamicObjectCast<ServerSession>(object); | ||||||
|             result = ReceiveIPCRequest(memory, server_session, thread); |             result = ReceiveIPCRequest(kernel, memory, server_session, thread); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         thread->SetWaitSynchronizationResult(result); |         thread->SetWaitSynchronizationResult(result); | ||||||
|  |  | ||||||
|  | @ -171,6 +171,7 @@ void ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& context) | ||||||
|     auto itr = handlers.find(header_code); |     auto itr = handlers.find(header_code); | ||||||
|     const FunctionInfoBase* info = itr == handlers.end() ? nullptr : &itr->second; |     const FunctionInfoBase* info = itr == handlers.end() ? nullptr : &itr->second; | ||||||
|     if (info == nullptr || info->handler_callback == nullptr) { |     if (info == nullptr || info->handler_callback == nullptr) { | ||||||
|  |         context.ReportUnimplemented(); | ||||||
|         return ReportUnimplementedFunction(context.CommandBuffer(), info); |         return ReportUnimplementedFunction(context.CommandBuffer(), info); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -179,6 +180,14 @@ void ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& context) | ||||||
|     handler_invoker(this, info->handler_callback, 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
 | // Module interface
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -64,6 +64,9 @@ public: | ||||||
| 
 | 
 | ||||||
|     void HandleSyncRequest(Kernel::HLERequestContext& context) override; |     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: | protected: | ||||||
|     /// Member-function pointer type of SyncRequest handlers.
 |     /// Member-function pointer type of SyncRequest handlers.
 | ||||||
|     template <typename Self> |     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); |     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)); |     registered_services.emplace(std::move(name), std::move(client_port)); | ||||||
|     return MakeResult(std::move(server_port)); |     return MakeResult(std::move(server_port)); | ||||||
| } | } | ||||||
|  | @ -65,4 +66,12 @@ ResultVal<std::shared_ptr<Kernel::ClientSession>> ServiceManager::ConnectToServi | ||||||
|     return client_port->Connect(); |     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
 | } // namespace Service::SM
 | ||||||
|  |  | ||||||
|  | @ -51,6 +51,8 @@ public: | ||||||
|                                                                    unsigned int max_sessions); |                                                                    unsigned int max_sessions); | ||||||
|     ResultVal<std::shared_ptr<Kernel::ClientPort>> GetServicePort(const std::string& name); |     ResultVal<std::shared_ptr<Kernel::ClientPort>> GetServicePort(const std::string& name); | ||||||
|     ResultVal<std::shared_ptr<Kernel::ClientSession>> ConnectToService(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> |     template <typename T> | ||||||
|     std::shared_ptr<T> GetService(const std::string& service_name) const { |     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.
 |     /// Map of registered services, retrieved using GetServicePort or ConnectToService.
 | ||||||
|     std::unordered_map<std::string, std::shared_ptr<Kernel::ClientPort>> registered_services; |     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
 | } // namespace Service::SM
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue