/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2013 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #ifndef XENIA_KERNEL_UTIL_SHIM_UTILS_H_ #define XENIA_KERNEL_UTIL_SHIM_UTILS_H_ #include #include #include #include "xenia/base/byte_order.h" #include "xenia/base/logging.h" #include "xenia/base/memory.h" #include "xenia/base/string_buffer.h" #include "xenia/cpu/export_resolver.h" #include "xenia/cpu/ppc/ppc_context.h" #include "xenia/kernel/kernel_state.h" DECLARE_bool(log_high_frequency_kernel_calls); namespace xe { namespace kernel { using PPCContext = xe::cpu::ppc::PPCContext; #define SHIM_CALL void __cdecl #define SHIM_SET_MAPPING(library_name, export_name, shim_data) \ export_resolver->SetFunctionMapping( \ library_name, ordinals::export_name, \ (xe::cpu::xe_kernel_export_shim_fn)export_name##_shim); #define SHIM_MEM_BASE ppc_context->virtual_membase #define SHIM_MEM_ADDR(a) (a ? (ppc_context->virtual_membase + a) : nullptr) #define SHIM_MEM_8(a) xe::load_and_swap(SHIM_MEM_ADDR(a)) #define SHIM_MEM_16(a) xe::load_and_swap(SHIM_MEM_ADDR(a)) #define SHIM_MEM_32(a) xe::load_and_swap(SHIM_MEM_ADDR(a)) #define SHIM_MEM_64(a) xe::load_and_swap(SHIM_MEM_ADDR(a)) #define SHIM_SET_MEM_8(a, v) xe::store_and_swap(SHIM_MEM_ADDR(a), v) #define SHIM_SET_MEM_16(a, v) xe::store_and_swap(SHIM_MEM_ADDR(a), v) #define SHIM_SET_MEM_32(a, v) xe::store_and_swap(SHIM_MEM_ADDR(a), v) #define SHIM_SET_MEM_64(a, v) xe::store_and_swap(SHIM_MEM_ADDR(a), v) namespace util { inline uint32_t get_arg_stack_ptr(PPCContext* ppc_context, uint8_t index) { return ((uint32_t)ppc_context->r[1]) + 0x54 + index * 8; } inline uint8_t get_arg_8(PPCContext* ppc_context, uint8_t index) { if (index <= 7) { return (uint8_t)ppc_context->r[3 + index]; } uint32_t stack_address = get_arg_stack_ptr(ppc_context, index - 8); return SHIM_MEM_8(stack_address); } inline uint16_t get_arg_16(PPCContext* ppc_context, uint8_t index) { if (index <= 7) { return (uint16_t)ppc_context->r[3 + index]; } uint32_t stack_address = get_arg_stack_ptr(ppc_context, index - 8); return SHIM_MEM_16(stack_address); } inline uint32_t get_arg_32(PPCContext* ppc_context, uint8_t index) { if (index <= 7) { return (uint32_t)ppc_context->r[3 + index]; } uint32_t stack_address = get_arg_stack_ptr(ppc_context, index - 8); return SHIM_MEM_32(stack_address); } inline uint64_t get_arg_64(PPCContext* ppc_context, uint8_t index) { if (index <= 7) { return ppc_context->r[3 + index]; } uint32_t stack_address = get_arg_stack_ptr(ppc_context, index - 8); return SHIM_MEM_64(stack_address); } } // namespace util #define SHIM_GET_ARG_8(n) util::get_arg_8(ppc_context, n) #define SHIM_GET_ARG_16(n) util::get_arg_16(ppc_context, n) #define SHIM_GET_ARG_32(n) util::get_arg_32(ppc_context, n) #define SHIM_GET_ARG_64(n) util::get_arg_64(ppc_context, n) #define SHIM_SET_RETURN_32(v) ppc_context->r[3] = (uint64_t)((int32_t)v) #define SHIM_STRUCT(type, address) \ reinterpret_cast(SHIM_MEM_ADDR(address)) namespace shim { class Param { public: struct Init { PPCContext* ppc_context; int ordinal; int float_ordinal; }; Param& operator=(const Param&) = delete; int ordinal() const { return ordinal_; } protected: Param() : ordinal_(-1) {} explicit Param(Init& init) : ordinal_(--init.ordinal) {} template void LoadValue(Init& init, V* out_value) { if (ordinal_ <= 7) { *out_value = V(init.ppc_context->r[3 + ordinal_]); } else { uint32_t stack_ptr = uint32_t(init.ppc_context->r[1]) + 0x54 + (ordinal_ - 8) * 8; *out_value = xe::load_and_swap(init.ppc_context->virtual_membase + stack_ptr); } } int ordinal_; }; template <> inline void Param::LoadValue(Param::Init& init, float* out_value) { *out_value = static_cast(init.ppc_context->f[1 + ++init.float_ordinal]); } template <> inline void Param::LoadValue(Param::Init& init, double* out_value) { *out_value = init.ppc_context->f[1 + ++init.float_ordinal]; } template class ParamBase : public Param { public: ParamBase() : Param(), value_(0) {} ParamBase(T value) : Param(), value_(value) {} ParamBase(Init& init) : Param(init) { LoadValue(init, &value_); } ParamBase& operator=(const T& other) { value_ = other; return *this; } operator T() const { return value_; } T value() const { return value_; } protected: T value_; }; class PointerParam : public ParamBase { public: PointerParam(Init& init) : ParamBase(init) { host_ptr_ = value_ ? init.ppc_context->virtual_membase + value_ : nullptr; } PointerParam(void* host_ptr) : ParamBase(), host_ptr_(host_ptr) {} PointerParam& operator=(void*& other) { host_ptr_ = other; return *this; } uint32_t guest_address() const { return value_; } uintptr_t host_address() const { return reinterpret_cast(host_ptr_); } template T as() const { return reinterpret_cast(host_ptr_); } template xe::be* as_array() const { return reinterpret_cast*>(host_ptr_); } operator void*() const { return host_ptr_; } operator uint8_t*() const { return reinterpret_cast(host_ptr_); } operator bool() const { return host_ptr_ != nullptr; } void* operator+(int offset) const { return reinterpret_cast(host_ptr_) + offset; } void Zero(size_t size) const { assert_not_null(host_ptr_); std::memset(host_ptr_, 0, size); } protected: void* host_ptr_; }; template class PrimitivePointerParam : public ParamBase { public: PrimitivePointerParam(Init& init) : ParamBase(init) { host_ptr_ = value_ ? reinterpret_cast*>( init.ppc_context->virtual_membase + value_) : nullptr; } PrimitivePointerParam(T* host_ptr) : ParamBase() { host_ptr_ = reinterpret_cast*>(host_ptr); } PrimitivePointerParam& operator=(const T*& other) { host_ptr_ = other; return *this; } uint32_t guest_address() const { return value_; } uintptr_t host_address() const { return reinterpret_cast(host_ptr_); } T value() const { return *host_ptr_; } operator T() const = delete; operator xe::be*() const { return host_ptr_; } operator bool() const { return host_ptr_ != nullptr; } void Zero() const { assert_not_null(host_ptr_); *host_ptr_ = 0; } protected: xe::be* host_ptr_; }; template class StringPointerParam : public ParamBase { public: StringPointerParam(Init& init) : ParamBase(init) { host_ptr_ = value_ ? reinterpret_cast( init.ppc_context->virtual_membase + value_) : nullptr; } StringPointerParam(CHAR* host_ptr) : ParamBase(), host_ptr_(host_ptr) {} StringPointerParam& operator=(const CHAR*& other) { host_ptr_ = other; return *this; } uint32_t guest_address() const { return value_; } uintptr_t host_address() const { return reinterpret_cast(host_ptr_); } STR value() const { return xe::load_and_swap(host_ptr_); } operator CHAR*() const { return host_ptr_; } operator bool() const { return host_ptr_ != nullptr; } protected: CHAR* host_ptr_; }; template class TypedPointerParam : public ParamBase { public: TypedPointerParam(Init& init) : ParamBase(init) { host_ptr_ = value_ ? reinterpret_cast(init.ppc_context->virtual_membase + value_) : nullptr; } TypedPointerParam(T* host_ptr) : ParamBase(), host_ptr_(host_ptr) {} TypedPointerParam& operator=(const T*& other) { host_ptr_ = other; return *this; } uint32_t guest_address() const { return value_; } uintptr_t host_address() const { return reinterpret_cast(host_ptr_); } operator T*() const { return host_ptr_; } operator bool() const { return host_ptr_ != nullptr; } T* operator->() const { assert_not_null(host_ptr_); return host_ptr_; } void Zero() const { assert_not_null(host_ptr_); std::memset(host_ptr_, 0, sizeof(T)); } protected: T* host_ptr_; }; template class Result { public: Result(T value) : value_(value) {} void Store(PPCContext* ppc_context) { ppc_context->r[3] = uint64_t(int32_t(value_)); } Result() = delete; Result& operator=(const Result&) = delete; operator T() const { return value_; } private: T value_; }; } // namespace shim using int_t = const shim::ParamBase&; using word_t = const shim::ParamBase&; using dword_t = const shim::ParamBase&; using qword_t = const shim::ParamBase&; using float_t = const shim::ParamBase&; using double_t = const shim::ParamBase&; using lpvoid_t = const shim::PointerParam&; using lpword_t = const shim::PrimitivePointerParam&; using lpdword_t = const shim::PrimitivePointerParam&; using lpqword_t = const shim::PrimitivePointerParam&; using lpfloat_t = const shim::PrimitivePointerParam&; using lpdouble_t = const shim::PrimitivePointerParam&; using lpstring_t = const shim::StringPointerParam&; using lpwstring_t = const shim::StringPointerParam&; using function_t = const shim::ParamBase&; using unknown_t = const shim::ParamBase&; using lpunknown_t = const shim::PointerParam&; template using pointer_t = const shim::TypedPointerParam&; using int_result_t = shim::Result; using dword_result_t = shim::Result; using pointer_result_t = shim::Result; using X_HRESULT_result_t = shim::Result; // Exported from kernel_state.cc. KernelState* kernel_state(); inline Memory* kernel_memory() { return kernel_state()->memory(); } namespace shim { inline void AppendParam(StringBuffer* string_buffer, int_t param) { string_buffer->AppendFormat("%d", int32_t(param)); } inline void AppendParam(StringBuffer* string_buffer, word_t param) { string_buffer->AppendFormat("%.4X", uint16_t(param)); } inline void AppendParam(StringBuffer* string_buffer, dword_t param) { string_buffer->AppendFormat("%.8X", uint32_t(param)); } inline void AppendParam(StringBuffer* string_buffer, qword_t param) { string_buffer->AppendFormat("%.16llX", uint64_t(param)); } inline void AppendParam(StringBuffer* string_buffer, float_t param) { string_buffer->AppendFormat("%G", static_cast(param)); } inline void AppendParam(StringBuffer* string_buffer, double_t param) { string_buffer->AppendFormat("%G", static_cast(param)); } inline void AppendParam(StringBuffer* string_buffer, lpvoid_t param) { string_buffer->AppendFormat("%.8X", uint32_t(param)); } inline void AppendParam(StringBuffer* string_buffer, lpdword_t param) { string_buffer->AppendFormat("%.8X", param.guest_address()); if (param) { string_buffer->AppendFormat("(%.8X)", param.value()); } } inline void AppendParam(StringBuffer* string_buffer, lpqword_t param) { string_buffer->AppendFormat("%.8X", param.guest_address()); if (param) { string_buffer->AppendFormat("(%.16llX)", param.value()); } } inline void AppendParam(StringBuffer* string_buffer, lpfloat_t param) { string_buffer->AppendFormat("%.8X", param.guest_address()); if (param) { string_buffer->AppendFormat("(%G)", param.value()); } } inline void AppendParam(StringBuffer* string_buffer, lpdouble_t param) { string_buffer->AppendFormat("%.8X", param.guest_address()); if (param) { string_buffer->AppendFormat("(%G)", param.value()); } } inline void AppendParam(StringBuffer* string_buffer, lpstring_t param) { string_buffer->AppendFormat("%.8X", param.guest_address()); if (param) { string_buffer->AppendFormat("(%s)", param.value().c_str()); } } inline void AppendParam(StringBuffer* string_buffer, lpwstring_t param) { string_buffer->AppendFormat("%.8X", param.guest_address()); if (param) { string_buffer->AppendFormat("(%S)", param.value().c_str()); } } inline void AppendParam(StringBuffer* string_buffer, pointer_t record) { string_buffer->AppendFormat("%.8X", record.guest_address()); if (record) { auto name_string = kernel_memory()->TranslateVirtual(record->name_ptr); std::string name = name_string == nullptr ? "(null)" : name_string->to_string(kernel_memory()->virtual_membase()); string_buffer->AppendFormat("(%.8X,%s,%.8X)", uint32_t(record->root_directory), name.c_str(), uint32_t(record->attributes)); } } inline void AppendParam(StringBuffer* string_buffer, pointer_t reg) { string_buffer->AppendFormat("%.8X(%.8X, %.8X)", reg.guest_address(), static_cast(reg->notification_routine), static_cast(reg->priority)); } inline void AppendParam(StringBuffer* string_buffer, pointer_t record) { string_buffer->AppendFormat("%.8X(%.8X)", record.guest_address(), uint32_t(record->exception_code)); } template void AppendParam(StringBuffer* string_buffer, pointer_t param) { string_buffer->AppendFormat("%.8X", param.guest_address()); } enum class KernelModuleId { xboxkrnl, xam, xbdm, }; template typename std::enable_if::type AppendKernelCallParams( StringBuffer& string_buffer, xe::cpu::Export* export_entry, const std::tuple&) {} template typename std::enable_if < I::type AppendKernelCallParams( StringBuffer& string_buffer, xe::cpu::Export* export_entry, const std::tuple& params) { if (I) { string_buffer.Append(", "); } auto param = std::get(params); AppendParam(&string_buffer, param); AppendKernelCallParams(string_buffer, export_entry, params); } StringBuffer* thread_local_string_buffer(); template void PrintKernelCall(cpu::Export* export_entry, const Tuple& params) { auto& string_buffer = *thread_local_string_buffer(); string_buffer.Reset(); string_buffer.Append(export_entry->name); string_buffer.Append('('); AppendKernelCallParams(string_buffer, export_entry, params); string_buffer.Append(')'); if (export_entry->tags & xe::cpu::ExportTag::kImportant) { xe::LogLine(xe::LogLevel::Info, 'i', string_buffer.GetString(), string_buffer.length()); } else { xe::LogLine(xe::LogLevel::Debug, 'd', string_buffer.GetString(), string_buffer.length()); } } template auto KernelTrampoline(F&& f, Tuple&& t, std::index_sequence) { return std::forward(f)(std::get(std::forward(t))...); } template xe::cpu::Export* RegisterExport(R (*fn)(Ps&...), const char* name, xe::cpu::ExportTag::type tags) { static const auto export_entry = new cpu::Export( ORDINAL, xe::cpu::Export::Type::kFunction, name, tags | xe::cpu::ExportTag::kImplemented | xe::cpu::ExportTag::kLog); static R (*FN)(Ps & ...) = fn; struct X { static void Trampoline(PPCContext* ppc_context) { ++export_entry->function_data.call_count; Param::Init init = { ppc_context, sizeof...(Ps), 0, }; auto params = std::make_tuple(Ps(init)...); if (export_entry->tags & xe::cpu::ExportTag::kLog && (!(export_entry->tags & xe::cpu::ExportTag::kHighFrequency) || FLAGS_log_high_frequency_kernel_calls)) { PrintKernelCall(export_entry, params); } auto result = KernelTrampoline(FN, std::forward>(params), std::make_index_sequence()); result.Store(ppc_context); if (export_entry->tags & (xe::cpu::ExportTag::kLog | xe::cpu::ExportTag::kLogResult)) { // TODO(benvanik): log result. } } }; export_entry->function_data.trampoline = &X::Trampoline; return export_entry; } template xe::cpu::Export* RegisterExport(void (*fn)(Ps&...), const char* name, xe::cpu::ExportTag::type tags) { static const auto export_entry = new cpu::Export( ORDINAL, xe::cpu::Export::Type::kFunction, name, tags | xe::cpu::ExportTag::kImplemented | xe::cpu::ExportTag::kLog); static void (*FN)(Ps & ...) = fn; struct X { static void Trampoline(PPCContext* ppc_context) { ++export_entry->function_data.call_count; Param::Init init = { ppc_context, sizeof...(Ps), }; auto params = std::make_tuple(Ps(init)...); if (export_entry->tags & xe::cpu::ExportTag::kLog && (!(export_entry->tags & xe::cpu::ExportTag::kHighFrequency) || FLAGS_log_high_frequency_kernel_calls)) { PrintKernelCall(export_entry, params); } KernelTrampoline(FN, std::forward>(params), std::make_index_sequence()); } }; export_entry->function_data.trampoline = &X::Trampoline; return export_entry; } } // namespace shim using xe::cpu::ExportTag; #define DECLARE_EXPORT(module_name, name, category, tags) \ const auto EXPORT_##module_name##_##name = RegisterExport_##module_name( \ xe::kernel::shim::RegisterExport< \ xe::kernel::shim::KernelModuleId::module_name, ordinals::name>( \ &name, #name, \ tags | (static_cast( \ xe::cpu::ExportCategory::category) \ << xe::cpu::ExportTag::CategoryShift))); #define DECLARE_XAM_EXPORT_(name, category, tags) \ DECLARE_EXPORT(xam, name, category, tags) #define DECLARE_XAM_EXPORT1(name, category, tag) \ DECLARE_EXPORT(xam, name, category, xe::cpu::ExportTag::tag) #define DECLARE_XAM_EXPORT2(name, category, tag1, tag2) \ DECLARE_EXPORT(xam, name, category, \ xe::cpu::ExportTag::tag1 | xe::cpu::ExportTag::tag2) #define DECLARE_XBDM_EXPORT_(name, category, tags) \ DECLARE_EXPORT(xbdm, name, category, tags) #define DECLARE_XBDM_EXPORT1(name, category, tag) \ DECLARE_EXPORT(xbdm, name, category, xe::cpu::ExportTag::tag) #define DECLARE_XBOXKRNL_EXPORT_(name, category, tags) \ DECLARE_EXPORT(xboxkrnl, name, category, tags) #define DECLARE_XBOXKRNL_EXPORT1(name, category, tag) \ DECLARE_EXPORT(xboxkrnl, name, category, xe::cpu::ExportTag::tag) #define DECLARE_XBOXKRNL_EXPORT2(name, category, tag1, tag2) \ DECLARE_EXPORT(xboxkrnl, name, category, \ xe::cpu::ExportTag::tag1 | xe::cpu::ExportTag::tag2) #define DECLARE_XBOXKRNL_EXPORT3(name, category, tag1, tag2, tag3) \ DECLARE_EXPORT(xboxkrnl, name, category, \ xe::cpu::ExportTag::tag1 | xe::cpu::ExportTag::tag2 | \ xe::cpu::ExportTag::tag3) #define DECLARE_XBOXKRNL_EXPORT4(name, category, tag1, tag2, tag3, tag4) \ DECLARE_EXPORT(xboxkrnl, name, category, \ xe::cpu::ExportTag::tag1 | xe::cpu::ExportTag::tag2 | \ xe::cpu::ExportTag::tag3 | xe::cpu::ExportTag::tag4) } // namespace kernel } // namespace xe #endif // XENIA_KERNEL_UTIL_SHIM_UTILS_H_