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.
This commit is contained in:
parent
71e0c40310
commit
a3057c968b
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
Reference in New Issue