From a3057c968bcf323d78f847b646e853d8d83e11b6 Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Mon, 22 Jul 2019 20:09:11 +0800 Subject: [PATCH] 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