From 71e0c40310df741414a54111965903f8afd931cf Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Mon, 22 Jul 2019 20:24:01 +0800 Subject: [PATCH 01/10] service: Add service function name lookup based on header code This is for displaying the function name for HLE requests. Probably it is possible to do the same for LLE ones but it would require having the HLE handlers available even when not using them, which doesn't seem to make sense and is more of a hack than a proper solution in my opinion. --- src/core/hle/service/service.cpp | 8 ++++++++ src/core/hle/service/service.h | 3 +++ 2 files changed, 11 insertions(+) diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 461d0930f..057562488 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -179,6 +179,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 diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 1d9e800e2..db6a0ad23 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -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 From a3057c968bcf323d78f847b646e853d8d83e11b6 Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Mon, 22 Jul 2019 20:09:11 +0800 Subject: [PATCH 02/10] kernel: Add IPC Recorder class This class resides in Kernel mainly because that, it's hard for kernel objects to get references to the System (and therefore to the Recorder), while much easier for KernelSystem. If this is to be moved to System the code will likely get much more complex with System (or Recorder) references passed everywhere. --- src/core/CMakeLists.txt | 2 + src/core/hle/kernel/ipc_debugger/recorder.cpp | 165 ++++++++++++++++++ src/core/hle/kernel/ipc_debugger/recorder.h | 129 ++++++++++++++ 3 files changed, 296 insertions(+) create mode 100644 src/core/hle/kernel/ipc_debugger/recorder.cpp create mode 100644 src/core/hle/kernel/ipc_debugger/recorder.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 0c7bbbecf..08554ce28 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -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 diff --git a/src/core/hle/kernel/ipc_debugger/recorder.cpp b/src/core/hle/kernel/ipc_debugger/recorder.cpp new file mode 100644 index 000000000..968815c5b --- /dev/null +++ b/src/core/hle/kernel/ipc_debugger/recorder.cpp @@ -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(object->GetObjectId())}; +} + +ObjectInfo GetObjectInfo(const Kernel::Thread* thread) { + if (thread == nullptr) { + return {}; + } + return {thread->GetTypeName(), thread->GetName(), static_cast(thread->GetThreadId())}; +} + +ObjectInfo GetObjectInfo(const Kernel::Process* process) { + if (process == nullptr) { + return {}; + } + return {process->GetTypeName(), process->GetName(), static_cast(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& client_session, + const std::shared_ptr& 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(record)); + client_session_map.insert_or_assign(thread_id, client_session); + + InvokeCallbacks(record); +} + +void Recorder::SetRequestInfo(const std::shared_ptr& client_thread, + std::vector untranslated_cmdbuf, + std::vector translated_cmdbuf, + const std::shared_ptr& 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( + 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& client_thread, + std::vector untranslated_cmdbuf, + std::vector 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& 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(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 diff --git a/src/core/hle/kernel/ipc_debugger/recorder.h b/src/core/hle/kernel/ipc_debugger/recorder.h new file mode 100644 index 000000000..ebd8bf5d1 --- /dev/null +++ b/src/core/hle/kernel/ipc_debugger/recorder.h @@ -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 +#include +#include +#include +#include +#include +#include +#include +#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 untranslated_request_cmdbuf; + std::vector translated_request_cmdbuf; + // Reply info is only available when status is `Handled` + std::vector untranslated_reply_cmdbuf; + std::vector translated_reply_cmdbuf; +}; + +using CallbackType = std::function; +using CallbackHandle = std::shared_ptr; + +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& client_session, + const std::shared_ptr& 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& client_thread, + std::vector untranslated_cmdbuf, std::vector translated_cmdbuf, + const std::shared_ptr& 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& client_thread, + std::vector untranslated_cmdbuf, std::vector translated_cmdbuf); + + /** + * Set the status of a record to HLEUnimplemented. + */ + void SetHLEUnimplemented(const std::shared_ptr& 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> record_map; + int record_count{}; + + // Temporary client session map for function name handling + std::unordered_map> client_session_map; + + std::atomic_bool enabled{false}; + + std::set callbacks; + mutable std::shared_mutex callback_mutex; +}; + +} // namespace IPCDebugger From b093d39a273d070ba763e32065d14bc637f4de1c Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Mon, 22 Jul 2019 20:10:47 +0800 Subject: [PATCH 03/10] kernel: Add IPC Recorder to KernelSystem Refer to the previous commit message for reasons why this is in kernel. --- src/core/hle/kernel/kernel.cpp | 10 ++++++++++ src/core/hle/kernel/kernel.h | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index f480f6362..ceb2f14f5 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -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(*this); thread_manager = std::make_unique(*this); timer_manager = std::make_unique(timing); + ipc_recorder = std::make_unique(); } /// 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 port) { named_ports.emplace(std::move(name), std::move(port)); } diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 38045a425..58f63938b 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -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 config_mem_handler; std::unique_ptr shared_page_handler; + + std::unique_ptr ipc_recorder; }; } // namespace Kernel From cb0bd6530c3d7dbb83ba18d970f962cb5ca79434 Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Mon, 22 Jul 2019 20:17:29 +0800 Subject: [PATCH 04/10] kernel/svc: Add request registering All necessary objects are available here, making this a great place for the registering part. --- src/core/hle/kernel/svc.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 88a0ff5ba..ef0f35237 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -387,7 +387,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 From a27dfc269a4d51d05089a4c8776db81bf4fadf39 Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Mon, 22 Jul 2019 20:19:03 +0800 Subject: [PATCH 05/10] kernel: Add LLE request/reply recording The 'translate' function is a great place to put this in IMO as it is possible to get both untranslated and translated cmdbufs. However a kernel reference has to be passed here, but it is not too hard fortunately. --- src/core/hle/kernel/ipc.cpp | 22 +++++++++++++++++++++- src/core/hle/kernel/ipc.h | 5 ++++- src/core/hle/kernel/svc.cpp | 25 +++++++++++++------------ 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/core/hle/kernel/ipc.cpp b/src/core/hle/kernel/ipc.cpp index c9616e566..3dacb9831 100644 --- a/src/core/hle/kernel/ipc.cpp +++ b/src/core/hle/kernel/ipc.cpp @@ -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 src_thread, +ResultCode TranslateCommandBuffer(Kernel::KernelSystem& kernel, Memory::MemorySystem& memory, + std::shared_ptr src_thread, std::shared_ptr dst_thread, VAddr src_address, VAddr dst_address, std::vector& mapped_buffer_context, @@ -37,6 +39,13 @@ ResultCode TranslateCommandBuffer(Memory::MemorySystem& memory, std::shared_ptr< std::array cmd_buf; memory.ReadBlock(*src_process, src_address, cmd_buf.data(), command_size * sizeof(u32)); + const bool should_record = kernel.GetIPCRecorder().IsEnabled(); + + std::vector untranslated_cmdbuf; + if (should_record) { + untranslated_cmdbuf = std::vector{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 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; diff --git a/src/core/hle/kernel/ipc.h b/src/core/hle/kernel/ipc.h index d84028767..b06079958 100644 --- a/src/core/hle/kernel/ipc.h +++ b/src/core/hle/kernel/ipc.h @@ -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 src_thread, +ResultCode TranslateCommandBuffer(KernelSystem& system, Memory::MemorySystem& memory, + std::shared_ptr src_thread, std::shared_ptr dst_thread, VAddr src_address, VAddr dst_address, std::vector& mapped_buffer_context, diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index ef0f35237..b5ebaf936 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -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" @@ -599,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 server_session, std::shared_ptr thread) { if (server_session->parent->client == nullptr) { @@ -609,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()) { @@ -676,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. @@ -713,7 +714,7 @@ ResultCode SVC::ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_co return RESULT_SUCCESS; auto server_session = static_cast(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. @@ -729,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, - std::shared_ptr object) { + thread->wakeup_callback = [& kernel = this->kernel, &memory = this->memory]( + ThreadWakeupReason reason, std::shared_ptr thread, + std::shared_ptr object) { ASSERT(thread->status == ThreadStatus::WaitSynchAny); ASSERT(reason == ThreadWakeupReason::Signal); @@ -739,7 +740,7 @@ ResultCode SVC::ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_co if (object->GetHandleType() == HandleType::ServerSession) { auto server_session = DynamicObjectCast(object); - result = ReceiveIPCRequest(memory, server_session, thread); + result = ReceiveIPCRequest(kernel, memory, server_session, thread); } thread->SetWaitSynchronizationResult(result); From efd69e13158e10700925b203e0d054c2aee9118d Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Mon, 22 Jul 2019 20:25:43 +0800 Subject: [PATCH 06/10] kernel, service: Add HLE request/reply recording Pretty much the same as LLE requests, the 'translate' part is chosen. A function is added to the context class to record requests that involves unimplemented HLE functions. --- src/core/hle/kernel/hle_ipc.cpp | 33 ++++++++++++++++++++++++++++++++ src/core/hle/kernel/hle_ipc.h | 3 +++ src/core/hle/service/service.cpp | 1 + 3 files changed, 37 insertions(+) diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index cd2f9fc87..ab4ecfd05 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -10,6 +10,7 @@ #include "core/hle/kernel/event.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/ipc_debugger/recorder.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" @@ -107,6 +108,13 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* sr std::copy_n(src_cmdbuf, untranslated_size, cmd_buf.begin()); + const bool should_record = kernel.GetIPCRecorder().IsEnabled(); + + std::vector untranslated_cmdbuf; + if (should_record) { + untranslated_cmdbuf = std::vector{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 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 untranslated_cmdbuf; + if (should_record) { + untranslated_cmdbuf = std::vector{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 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) { diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index f684985d0..26942fe6b 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -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 cmd_buf; diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 057562488..0310d4ad9 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -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); } From f40232adc97da95456b63c051e9cb75a9e001322 Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Mon, 22 Jul 2019 20:27:35 +0800 Subject: [PATCH 07/10] service/sm: Add service name retrival based on client port ID This is for displaying the service names. This function is only used in the frontend, because Recorder which is in the Kernel cannot and should not have access to SM in the System. --- src/core/hle/service/sm/sm.cpp | 9 +++++++++ src/core/hle/service/sm/sm.h | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index e37c72cc1..3caca2f93 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp @@ -42,6 +42,7 @@ ResultVal> 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> 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 diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h index dc26d8814..6e47fd152 100644 --- a/src/core/hle/service/sm/sm.h +++ b/src/core/hle/service/sm/sm.h @@ -51,6 +51,8 @@ public: unsigned int max_sessions); ResultVal> GetServicePort(const std::string& name); ResultVal> ConnectToService(const std::string& name); + // For IPC Recorder + std::string GetServiceNameByPortId(u32 port) const; template std::shared_ptr GetService(const std::string& service_name) const { @@ -74,6 +76,10 @@ private: /// Map of registered services, retrieved using GetServicePort or ConnectToService. std::unordered_map> registered_services; + + // For IPC Recorder + /// client port Object id -> service name + std::unordered_map registered_services_inverse; }; } // namespace Service::SM From 45930e0621f92e949e8083eb1967e6cae62290d0 Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Mon, 22 Jul 2019 20:47:47 +0800 Subject: [PATCH 08/10] citra_qt: Add record dialog This 'View Record' dialog shows all relevant information on a record as well as the command buffers. --- src/citra_qt/CMakeLists.txt | 3 + src/citra_qt/debugger/ipc/record_dialog.cpp | 64 +++++ src/citra_qt/debugger/ipc/record_dialog.h | 36 +++ src/citra_qt/debugger/ipc/record_dialog.ui | 281 ++++++++++++++++++++ 4 files changed, 384 insertions(+) create mode 100644 src/citra_qt/debugger/ipc/record_dialog.cpp create mode 100644 src/citra_qt/debugger/ipc/record_dialog.h create mode 100644 src/citra_qt/debugger/ipc/record_dialog.ui diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 6ac1eb2db..9d5137a4f 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -90,6 +90,9 @@ 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/lle_service_modules.cpp debugger/lle_service_modules.h debugger/profiler.cpp diff --git a/src/citra_qt/debugger/ipc/record_dialog.cpp b/src/citra_qt/debugger/ipc/record_dialog.cpp new file mode 100644 index 000000000..2a179e10f --- /dev/null +++ b/src/citra_qt/debugger/ipc/record_dialog.cpp @@ -0,0 +1,64 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#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& 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->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(&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])); +} diff --git a/src/citra_qt/debugger/ipc/record_dialog.h b/src/citra_qt/debugger/ipc/record_dialog.h new file mode 100644 index 000000000..40618b925 --- /dev/null +++ b/src/citra_qt/debugger/ipc/record_dialog.h @@ -0,0 +1,36 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#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& cmdbuf) const; + void UpdateCmdbufDisplay(); + + std::unique_ptr ui; + std::array, 4> cmdbufs; +}; diff --git a/src/citra_qt/debugger/ipc/record_dialog.ui b/src/citra_qt/debugger/ipc/record_dialog.ui new file mode 100644 index 000000000..44c70d386 --- /dev/null +++ b/src/citra_qt/debugger/ipc/record_dialog.ui @@ -0,0 +1,281 @@ + + + RecordDialog + + + View Record + + + + 0 + 0 + 800 + 500 + + + + + + + + + Client + + + + + + + + Process: + + + + + + + true + + + + + + + + + + + Thread: + + + + + + + true + + + + + + + + + + + Session: + + + + + + + true + + + + + + + + + + + + Server + + + + + + + + Process: + + + + + + + true + + + + + + + + + + + Thread: + + + + + + + true + + + + + + + + + + + Session: + + + + + + + true + + + + + + + + + + + + + + General + + + + + + + + Client Port: + + + + + + + true + + + + + + + + + + + Service: + + + + + + + true + + + + + + + + + + + Function: + + + + + + + true + + + + + + + + + + + + Command Buffer + + + + + + + + Select: + + + + + + + + Request Untranslated + + + + + Request Translated + + + + + Reply Untranslated + + + + + Reply Translated + + + + + + + + + + true + + + + + + + + + + + + Qt::Horizontal + + + + + + + OK + + + + + + + + From 42cefdbff0dd1127a008aa29f9ab4eaaf437012d Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Mon, 22 Jul 2019 20:49:39 +0800 Subject: [PATCH 09/10] citra_qt/debugger: Add recorder widget This widget provides a simple list of recorded requests as well as a simple filter and the 'Clear' button. Requests with status 'HLE Unimplemented' or 'Error' will be marked out with the red color. This widget handles retrival of service name. For reasons refer to a previous commit message. Added the widget to the Debugging menu, pretty much the same as other debugging widgets. --- src/citra_qt/CMakeLists.txt | 3 + src/citra_qt/debugger/ipc/recorder.cpp | 183 +++++++++++++++++++++++++ src/citra_qt/debugger/ipc/recorder.h | 52 +++++++ src/citra_qt/debugger/ipc/recorder.ui | 93 +++++++++++++ src/citra_qt/main.cpp | 8 ++ src/citra_qt/main.h | 2 + 6 files changed, 341 insertions(+) create mode 100644 src/citra_qt/debugger/ipc/recorder.cpp create mode 100644 src/citra_qt/debugger/ipc/recorder.h create mode 100644 src/citra_qt/debugger/ipc/recorder.ui diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 9d5137a4f..0198ada76 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -93,6 +93,9 @@ add_executable(citra-qt debugger/ipc/record_dialog.cpp debugger/ipc/record_dialog.h debugger/ipc/record_dialog.ui + debugger/ipc/recorder.cpp + debugger/ipc/recorder.h + debugger/ipc/recorder.ui debugger/lle_service_modules.cpp debugger/lle_service_modules.h debugger/profiler.cpp diff --git a/src/citra_qt/debugger/ipc/recorder.cpp b/src/citra_qt/debugger/ipc/recorder.cpp new file mode 100644 index 000000000..24128e38a --- /dev/null +++ b/src/citra_qt/debugger/ipc/recorder.cpp @@ -0,0 +1,183 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include "citra_qt/debugger/ipc/record_dialog.h" +#include "citra_qt/debugger/ipc/recorder.h" +#include "common/assert.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/hle/kernel/ipc_debugger/recorder.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/service/sm/sm.h" +#include "ui_recorder.h" + +IPCRecorderWidget::IPCRecorderWidget(QWidget* parent) + : QDockWidget(parent), ui(std::make_unique()) { + + ui->setupUi(this); + qRegisterMetaType(); + + connect(ui->enabled, &QCheckBox::stateChanged, + [this](int new_state) { SetEnabled(new_state == Qt::Checked); }); + connect(ui->clearButton, &QPushButton::clicked, this, &IPCRecorderWidget::Clear); + connect(ui->filter, &QLineEdit::textChanged, this, &IPCRecorderWidget::ApplyFilterToAll); + connect(ui->main, &QTreeWidget::itemDoubleClicked, this, &IPCRecorderWidget::OpenRecordDialog); + connect(this, &IPCRecorderWidget::EntryUpdated, this, &IPCRecorderWidget::OnEntryUpdated); +} + +IPCRecorderWidget::~IPCRecorderWidget() = default; + +void IPCRecorderWidget::OnEmulationStarting() { + Clear(); + id_offset = 1; + + // Update the enabled status when the system is powered on. + SetEnabled(ui->enabled->isChecked()); +} + +QString IPCRecorderWidget::GetStatusStr(const IPCDebugger::RequestRecord& record) const { + switch (record.status) { + case IPCDebugger::RequestStatus::Invalid: + return tr("Invalid"); + case IPCDebugger::RequestStatus::Sent: + return tr("Sent"); + case IPCDebugger::RequestStatus::Handling: + return tr("Handling"); + case IPCDebugger::RequestStatus::Handled: + if (record.translated_reply_cmdbuf[1] == RESULT_SUCCESS.raw) { + return tr("Success"); + } + return tr("Error"); + case IPCDebugger::RequestStatus::HLEUnimplemented: + return tr("HLE Unimplemented"); + default: + UNREACHABLE(); + } +} + +void IPCRecorderWidget::OnEntryUpdated(IPCDebugger::RequestRecord record) { + if (record.id < id_offset) { // The record has already been deleted by 'Clear' + return; + } + + QString service = GetServiceName(record); + if (record.status == IPCDebugger::RequestStatus::Handling || + record.status == IPCDebugger::RequestStatus::Handled || + record.status == IPCDebugger::RequestStatus::HLEUnimplemented) { + + service = QStringLiteral("%1 (%2)").arg(service, record.is_hle ? tr("HLE") : tr("LLE")); + } + + QTreeWidgetItem item{ + {QString::number(record.id), GetStatusStr(record), service, GetFunctionName(record)}}; + + const int row_id = record.id - id_offset; + if (ui->main->invisibleRootItem()->childCount() > row_id) { + records[row_id] = record; + (*ui->main->invisibleRootItem()->child(row_id)) = item; + } else { + records.emplace_back(record); + ui->main->invisibleRootItem()->addChild(new QTreeWidgetItem(item)); + } + + if (record.status == IPCDebugger::RequestStatus::HLEUnimplemented || + (record.status == IPCDebugger::RequestStatus::Handled && + record.translated_reply_cmdbuf[1] != RESULT_SUCCESS.raw)) { // Unimplemented / Error + + auto* item = ui->main->invisibleRootItem()->child(row_id); + for (int column = 0; column < item->columnCount(); ++column) { + item->setBackgroundColor(column, QColor::fromRgb(255, 0, 0)); + } + } + + ApplyFilter(row_id); +} + +void IPCRecorderWidget::SetEnabled(bool enabled) { + if (!Core::System::GetInstance().IsPoweredOn()) { + return; + } + + auto& ipc_recorder = Core::System::GetInstance().Kernel().GetIPCRecorder(); + ipc_recorder.SetEnabled(enabled); + + if (enabled) { + handle = ipc_recorder.BindCallback( + [this](const IPCDebugger::RequestRecord& record) { emit EntryUpdated(record); }); + } else if (handle) { + ipc_recorder.UnbindCallback(handle); + } +} + +void IPCRecorderWidget::Clear() { + id_offset = records.size() + 1; + + records.clear(); + ui->main->invisibleRootItem()->takeChildren(); +} + +QString IPCRecorderWidget::GetServiceName(const IPCDebugger::RequestRecord& record) const { + if (Core::System::GetInstance().IsPoweredOn() && record.client_port.id != -1) { + const auto service_name = + Core::System::GetInstance().ServiceManager().GetServiceNameByPortId( + static_cast(record.client_port.id)); + + if (!service_name.empty()) { + return QString::fromStdString(service_name); + } + } + + // Get a similar result from the server session name + std::string session_name = record.server_session.name; + session_name = Common::ReplaceAll(session_name, "_Server", ""); + session_name = Common::ReplaceAll(session_name, "_Client", ""); + return QString::fromStdString(session_name); +} + +QString IPCRecorderWidget::GetFunctionName(const IPCDebugger::RequestRecord& record) const { + if (record.untranslated_request_cmdbuf.empty()) { // Cmdbuf is not yet available + return tr("Unknown"); + } + const QString header_code = + QStringLiteral("0x%1").arg(record.untranslated_request_cmdbuf[0], 8, 16, QLatin1Char('0')); + if (record.function_name.empty()) { + return header_code; + } + return QStringLiteral("%1 (%2)").arg(QString::fromStdString(record.function_name), header_code); +} + +void IPCRecorderWidget::ApplyFilter(int index) { + auto* item = ui->main->invisibleRootItem()->child(index); + const QString filter = ui->filter->text(); + if (filter.isEmpty()) { + item->setHidden(false); + return; + } + + for (int i = 0; i < item->columnCount(); ++i) { + if (item->text(i).contains(filter)) { + item->setHidden(false); + return; + } + } + + item->setHidden(true); +} + +void IPCRecorderWidget::ApplyFilterToAll() { + for (int i = 0; i < ui->main->invisibleRootItem()->childCount(); ++i) { + ApplyFilter(i); + } +} + +void IPCRecorderWidget::OpenRecordDialog(QTreeWidgetItem* item, [[maybe_unused]] int column) { + int index = ui->main->invisibleRootItem()->indexOfChild(item); + + RecordDialog dialog(this, records[static_cast(index)], item->text(2), + item->text(3)); + dialog.exec(); +} diff --git a/src/citra_qt/debugger/ipc/recorder.h b/src/citra_qt/debugger/ipc/recorder.h new file mode 100644 index 000000000..63b190790 --- /dev/null +++ b/src/citra_qt/debugger/ipc/recorder.h @@ -0,0 +1,52 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include "core/hle/kernel/ipc_debugger/recorder.h" + +class QTreeWidgetItem; + +namespace Ui { +class IPCRecorder; +} + +class IPCRecorderWidget : public QDockWidget { + Q_OBJECT + +public: + explicit IPCRecorderWidget(QWidget* parent = nullptr); + ~IPCRecorderWidget(); + + void OnEmulationStarting(); + +signals: + void EntryUpdated(IPCDebugger::RequestRecord record); + +private: + QString GetStatusStr(const IPCDebugger::RequestRecord& record) const; + void OnEntryUpdated(IPCDebugger::RequestRecord record); + void SetEnabled(bool enabled); + void Clear(); + void ApplyFilter(int index); + void ApplyFilterToAll(); + QString GetServiceName(const IPCDebugger::RequestRecord& record) const; + QString GetFunctionName(const IPCDebugger::RequestRecord& record) const; + void OpenRecordDialog(QTreeWidgetItem* item, int column); + + std::unique_ptr ui; + IPCDebugger::CallbackHandle handle; + + // The offset between record id and row id, assuming record ids are assigned + // continuously and only the 'Clear' action can be performed, this is enough. + // The initial value is 1, which means record 1 = row 0. + int id_offset = 1; + std::vector records; +}; + +Q_DECLARE_METATYPE(IPCDebugger::RequestRecord); diff --git a/src/citra_qt/debugger/ipc/recorder.ui b/src/citra_qt/debugger/ipc/recorder.ui new file mode 100644 index 000000000..78b988fdc --- /dev/null +++ b/src/citra_qt/debugger/ipc/recorder.ui @@ -0,0 +1,93 @@ + + + IPCRecorder + + + + 0 + 0 + 600 + 300 + + + + IPC Recorder + + + + + + + Enable Recording + + + + + + + + + Filter: + + + + + + + Leave empty to disable filtering + + + + + + + + + true + + + + # + + + + + Status + + + + + Service + + + + + Function + + + + + + + + + + Qt::Horizontal + + + + + + + Clear + + + + + + + + + + + diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index d1a31fa5c..49e4588ae 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -36,6 +36,7 @@ #include "citra_qt/debugger/graphics/graphics_surface.h" #include "citra_qt/debugger/graphics/graphics_tracing.h" #include "citra_qt/debugger/graphics/graphics_vertex_shader.h" +#include "citra_qt/debugger/ipc/recorder.h" #include "citra_qt/debugger/lle_service_modules.h" #include "citra_qt/debugger/profiler.h" #include "citra_qt/debugger/registers.h" @@ -328,6 +329,13 @@ void GMainWindow::InitializeDebugWidgets() { [this] { lleServiceModulesWidget->setDisabled(true); }); connect(this, &GMainWindow::EmulationStopping, waitTreeWidget, [this] { lleServiceModulesWidget->setDisabled(false); }); + + ipcRecorderWidget = new IPCRecorderWidget(this); + addDockWidget(Qt::RightDockWidgetArea, ipcRecorderWidget); + ipcRecorderWidget->hide(); + debug_menu->addAction(ipcRecorderWidget->toggleViewAction()); + connect(this, &GMainWindow::EmulationStarting, ipcRecorderWidget, + &IPCRecorderWidget::OnEmulationStarting); } void GMainWindow::InitializeRecentFileMenuActions() { diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index 780589141..c5861cb2e 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -30,6 +30,7 @@ class GraphicsBreakPointsWidget; class GraphicsTracingWidget; class GraphicsVertexShaderWidget; class GRenderWindow; +class IPCRecorderWidget; class LLEServiceModulesWidget; class MicroProfileDialog; class MultiplayerState; @@ -247,6 +248,7 @@ private: GraphicsBreakPointsWidget* graphicsBreakpointsWidget; GraphicsVertexShaderWidget* graphicsVertexShaderWidget; GraphicsTracingWidget* graphicsTracingWidget; + IPCRecorderWidget* ipcRecorderWidget; LLEServiceModulesWidget* lleServiceModulesWidget; WaitTreeWidget* waitTreeWidget; Updater* updater; From 3d14abeb4118974128c25539711ad1f86dade6be Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Thu, 29 Aug 2019 00:03:56 +0800 Subject: [PATCH 10/10] citra_qt/record_dialog: Switch to QFormLayout --- src/citra_qt/debugger/ipc/record_dialog.ui | 276 +++++++++------------ 1 file changed, 120 insertions(+), 156 deletions(-) diff --git a/src/citra_qt/debugger/ipc/record_dialog.ui b/src/citra_qt/debugger/ipc/record_dialog.ui index 44c70d386..0f5c7a435 100644 --- a/src/citra_qt/debugger/ipc/record_dialog.ui +++ b/src/citra_qt/debugger/ipc/record_dialog.ui @@ -21,60 +21,48 @@ Client - - - - - - - Process: - - - - - - - true - - - - + + + + + Process: + + - - - - - - Thread: - - - - - - - true - - - - + + + + true + + - - - - - - Session: - - - - - - - true - - - - + + + + Thread: + + + + + + + true + + + + + + + Session: + + + + + + + true + + @@ -84,60 +72,48 @@ Server - - - - - - - Process: - - - - - - - true - - - - + + + + + Process: + + - - - - - - Thread: - - - - - - - true - - - - + + + + true + + - - - - - - Session: - - - - - - - true - - - - + + + + Thread: + + + + + + + true + + + + + + + Session: + + + + + + + true + + @@ -149,60 +125,48 @@ General - - - - - - - Client Port: - - - - - - - true - - - - + + + + + Client Port: + + - - - - - - Service: - - - - - - - true - - - - + + + + true + + - - - - - - Function: - - - - - - - true - - - - + + + + Service: + + + + + + + true + + + + + + + Function: + + + + + + + true + +