/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2022 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 "third_party/fmt/include/fmt/format.h" #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_flags.h" #include "xenia/kernel/kernel_state.h" namespace xe { namespace kernel { using PPCContext = xe::cpu::ppc::PPCContext; #define SHIM_CALL void #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##_entry); #define SHIM_MEM_ADDR(a) \ ((a) ? ppc_context->kernel_state->memory()->TranslateVirtual(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); } inline std::string TranslateAnsiString(const Memory* memory, const X_ANSI_STRING* ansi_string) { if (!ansi_string || !ansi_string->length) { return ""; } return std::string( memory->TranslateVirtual(ansi_string->pointer), ansi_string->length); } inline std::string TranslateAnsiStringAddress(const Memory* memory, uint32_t guest_address) { if (!guest_address) { return ""; } return TranslateAnsiString( memory, memory->TranslateVirtual(guest_address)); } inline std::u16string TranslateUnicodeString( const Memory* memory, const X_UNICODE_STRING* unicode_string) { if (!unicode_string) { return u""; } uint16_t length = unicode_string->length; if (!length) { return u""; } const xe::be* guest_string = memory->TranslateVirtual*>( unicode_string->pointer); std::u16string translated_string; translated_string.reserve(length); for (uint16_t i = 0; i < length; ++i) { translated_string += char16_t(uint16_t(guest_string[i])); } return translated_string; } } // 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->kernel_state->memory()->TranslateVirtual( 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->kernel_state->memory()->TranslateVirtual(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_ ? init.ppc_context->kernel_state->memory() ->TranslateVirtual*>(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_ ? init.ppc_context->kernel_state->memory()->TranslateVirtual( 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_ ? init.ppc_context->kernel_state->memory()->TranslateVirtual( 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_; }; class Result { public: virtual void Store(PPCContext* ppc_context) = 0; }; template class ResultBase : public Result { public: ResultBase(T value) : value_(value) {} void Store(PPCContext* ppc_context) { ppc_context->r[3] = uint64_t(int32_t(value_)); } ResultBase() = delete; ResultBase& operator=(const ResultBase&) = 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 lpu16string_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::ResultBase; using dword_result_t = shim::ResultBase; using pointer_result_t = shim::ResultBase; using X_HRESULT_result_t = shim::ResultBase; // 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("{}", int32_t(param)); } inline void AppendParam(StringBuffer* string_buffer, word_t param) { string_buffer->AppendFormat("{:04X}", uint16_t(param)); } inline void AppendParam(StringBuffer* string_buffer, dword_t param) { string_buffer->AppendFormat("{:08X}", uint32_t(param)); } inline void AppendParam(StringBuffer* string_buffer, qword_t param) { string_buffer->AppendFormat("{:016X}", 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("{:08X}", uint32_t(param)); } inline void AppendParam(StringBuffer* string_buffer, lpdword_t param) { string_buffer->AppendFormat("{:08X}", param.guest_address()); if (param) { string_buffer->AppendFormat("({:08X})", param.value()); } } inline void AppendParam(StringBuffer* string_buffer, lpqword_t param) { string_buffer->AppendFormat("{:08X}", param.guest_address()); if (param) { string_buffer->AppendFormat("({:016X})", param.value()); } } inline void AppendParam(StringBuffer* string_buffer, lpfloat_t param) { string_buffer->AppendFormat("{:08X}", param.guest_address()); if (param) { string_buffer->AppendFormat("({:G})", param.value()); } } inline void AppendParam(StringBuffer* string_buffer, lpdouble_t param) { string_buffer->AppendFormat("{:08X}", param.guest_address()); if (param) { string_buffer->AppendFormat("({:G})", param.value()); } } inline void AppendParam(StringBuffer* string_buffer, lpstring_t param) { string_buffer->AppendFormat("{:08X}", param.guest_address()); if (param) { string_buffer->AppendFormat("({})", param.value()); } } inline void AppendParam(StringBuffer* string_buffer, lpu16string_t param) { string_buffer->AppendFormat("{:08X}", param.guest_address()); if (param) { string_buffer->AppendFormat("({})", xe::to_utf8(param.value())); } } inline void AppendParam(StringBuffer* string_buffer, pointer_t record) { string_buffer->AppendFormat("{:08X}", record.guest_address()); if (record) { auto name_string = kernel_memory()->TranslateVirtual(record->name_ptr); std::string name = name_string == nullptr ? "(null)" : util::TranslateAnsiString(kernel_memory(), name_string); string_buffer->AppendFormat("({:08X},{},{:08X})", uint32_t(record->root_directory), name, uint32_t(record->attributes)); } } inline void AppendParam(StringBuffer* string_buffer, pointer_t reg) { string_buffer->AppendFormat("{:08X}({:08X}, {:08X})", reg.guest_address(), static_cast(reg->notification_routine), static_cast(reg->priority)); } inline void AppendParam(StringBuffer* string_buffer, pointer_t record) { string_buffer->AppendFormat("{:08X}({:08X})", record.guest_address(), uint32_t(record->exception_code)); } template void AppendParam(StringBuffer* string_buffer, pointer_t param) { string_buffer->AppendFormat("{:08X}", 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::logging::AppendLogLine(xe::LogLevel::Info, 'i', string_buffer.to_string_view()); } else { xe::logging::AppendLogLine(xe::LogLevel::Debug, 'd', string_buffer.to_string_view()); } } 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_assert( std::is_void::value || std::is_base_of::value, "R must be void or derive from shim::Result"); static_assert((std::is_base_of_v && ...), "Ps must derive from shim::Param"); 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, 0, }; // Using braces initializer instead of make_tuple because braces // enforce execution order across compilers. // The make_tuple order is undefined per the C++ standard and // cause inconsitencies between msvc and clang. std::tuple params = {Ps(init)...}; if (export_entry->tags & xe::cpu::ExportTag::kLog && (!(export_entry->tags & xe::cpu::ExportTag::kHighFrequency) || cvars::log_high_frequency_kernel_calls)) { PrintKernelCall(export_entry, params); } if constexpr (std::is_void::value) { KernelTrampoline(FN, std::forward>(params), std::make_index_sequence()); } else { 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; } } // 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##_entry, #name, \ tags | (static_cast( \ xe::cpu::ExportCategory::category) \ << xe::cpu::ExportTag::CategoryShift))); #define DECLARE_EMPTY_REGISTER_EXPORTS(module_name, group_name) \ void xe::kernel::module_name::Register##group_name##Exports( \ xe::cpu::ExportResolver* export_resolver, \ xe::kernel::KernelState* kernel_state) {} #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_XAM_EMPTY_REGISTER_EXPORTS(group_name) \ DECLARE_EMPTY_REGISTER_EXPORTS(xam, group_name) #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_XBDM_EMPTY_REGISTER_EXPORTS(group_name) \ DECLARE_EMPTY_REGISTER_EXPORTS(xbdm, group_name) #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) #define DECLARE_XBOXKRNL_EMPTY_REGISTER_EXPORTS(group_name) \ DECLARE_EMPTY_REGISTER_EXPORTS(xboxkrnl, group_name) } // namespace kernel } // namespace xe #endif // XENIA_KERNEL_UTIL_SHIM_UTILS_H_