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