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:
zhupengfei 2019-07-22 20:09:11 +08:00
parent 71e0c40310
commit a3057c968b
No known key found for this signature in database
GPG key ID: DD129E108BD09378
3 changed files with 296 additions and 0 deletions

View file

@ -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

View 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

View 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