#pragma once #include #include #include class thread_ctrl; namespace vm { extern u8* const g_base_addr; extern u8* const g_priv_addr; enum memory_location_t : uint { main, user_space, video, stack, memory_location_max, any = 0xffffffff, }; enum page_info_t : u8 { page_readable = (1 << 0), page_writable = (1 << 1), page_executable = (1 << 2), page_fault_notification = (1 << 3), page_no_reservations = (1 << 4), page_allocated = (1 << 7), }; // Address type enum addr_t : u32 {}; struct access_violation : std::runtime_error { access_violation(u64 addr, const char* cause); }; [[noreturn]] void throw_access_violation(u64 addr, const char* cause); // This flag is changed by various reservation functions and may have different meaning. // reservation_break() - true if the reservation was successfully broken. // reservation_acquire() - true if another existing reservation was broken. // reservation_free() - true if this thread's reservation was successfully removed. // reservation_op() - false if reservation_update() would succeed if called instead. // Write access to reserved memory - only set to true if the reservation was broken. extern thread_local bool g_tls_did_break_reservation; // Unconditionally break the reservation at specified address void reservation_break(u32 addr); // Reserve memory at the specified address for further atomic update void reservation_acquire(void* data, u32 addr, u32 size); // Attempt to atomically update previously reserved memory bool reservation_update(u32 addr, const void* data, u32 size); // Process a memory access error if it's caused by the reservation bool reservation_query(u32 addr, u32 size, bool is_writing, std::function callback); // Returns true if the current thread owns reservation bool reservation_test(thread_ctrl* current); // Break all reservations created by the current thread void reservation_free(); // Perform atomic operation unconditionally void reservation_op(u32 addr, u32 size, std::function proc); // Change memory protection of specified memory region bool page_protect(u32 addr, u32 size, u8 flags_test = 0, u8 flags_set = 0, u8 flags_clear = 0); // Check if existing memory range is allocated. Checking address before using it is very unsafe. // Return value may be wrong. Even if it's true and correct, actual memory protection may be read-only and no-access. bool check_addr(u32 addr, u32 size = 1); // Search and map memory in specified memory location (don't pass alignment smaller than 4096) u32 alloc(u32 size, memory_location_t location, u32 align = 4096, u32 sup = 0); // Map memory at specified address (in optionally specified memory location) u32 falloc(u32 addr, u32 size, memory_location_t location = any, u32 sup = 0); // Unmap memory at specified address (in optionally specified memory location), return size u32 dealloc(u32 addr, memory_location_t location = any, u32* sup_out = nullptr); // dealloc() with no return value and no exceptions void dealloc_verbose_nothrow(u32 addr, memory_location_t location = any) noexcept; // Object that handles memory allocations inside specific constant bounds ("location") class block_t final { std::map m_map; // Mapped memory: addr -> size std::unordered_map m_sup; // Supplementary info for allocations bool try_alloc(u32 addr, u32 size, u32 sup); public: block_t(u32 addr, u32 size, u64 flags = 0); ~block_t(); public: const u32 addr; // Start address const u32 size; // Total size const u64 flags; // Currently unused // Search and map memory (don't pass alignment smaller than 4096) u32 alloc(u32 size, u32 align = 4096, u32 sup = 0); // Try to map memory at fixed location u32 falloc(u32 addr, u32 size, u32 sup = 0); // Unmap memory at specified location previously returned by alloc(), return size u32 dealloc(u32 addr, u32* sup_out = nullptr); // Get allocated memory count u32 used(); }; // Create new memory block with specified parameters and return it std::shared_ptr map(u32 addr, u32 size, u64 flags = 0); // Delete existing memory block with specified start address, return it std::shared_ptr unmap(u32 addr, bool must_be_empty = false); // Get memory block associated with optionally specified memory location or optionally specified address std::shared_ptr get(memory_location_t location, u32 addr = 0); // Get PS3/PSV virtual memory address from the provided pointer (nullptr always converted to 0) inline vm::addr_t get_addr(const void* real_ptr) { if (!real_ptr) { return vm::addr_t{}; } const std::ptrdiff_t diff = static_cast(real_ptr) - g_base_addr; const u32 res = static_cast(diff); if (res == diff) { return static_cast(res); } throw fmt::exception("Not a virtual memory pointer (%p)", real_ptr); } // Convert pointer-to-member to a vm address compatible offset template inline u32 get_offset(MT T::*const member_ptr) { return static_cast(reinterpret_cast(&reinterpret_cast(reinterpret_cast(0ull)->*member_ptr))); } template struct cast_impl { static_assert(std::is_same::value, "vm::cast() error: unsupported type"); }; template<> struct cast_impl { static vm::addr_t cast(u32 addr, const char* loc) { return static_cast(addr); } static vm::addr_t cast(u32 addr) { return static_cast(addr); } }; template<> struct cast_impl { static vm::addr_t cast(u64 addr, const char* loc) { return static_cast(static_cast(addr)); } static vm::addr_t cast(u64 addr) { return static_cast(static_cast(addr)); } }; template struct cast_impl> { static vm::addr_t cast(const se_t& addr, const char* loc) { return cast_impl::cast(addr, loc); } static vm::addr_t cast(const se_t& addr) { return cast_impl::cast(addr); } }; template vm::addr_t cast(const T& addr, const char* loc) { return cast_impl::cast(addr, loc); } template vm::addr_t cast(const T& addr) { return cast_impl::cast(addr); } // Convert specified PS3/PSV virtual memory address to a pointer for common access inline void* base(u32 addr) { return g_base_addr + addr; } // Convert specified PS3/PSV virtual memory address to a pointer for privileged access (always readable/writable if allocated) inline void* base_priv(u32 addr) { return g_priv_addr + addr; } inline const u8& read8(u32 addr) { return g_base_addr[addr]; } inline void write8(u32 addr, u8 value) { g_base_addr[addr] = value; } namespace ps3 { // Convert specified PS3 address to a pointer of specified (possibly converted to BE) type template inline to_be_t* _ptr(u32 addr) { return static_cast*>(base(addr)); } template inline to_be_t* _ptr_priv(u32 addr) { return static_cast*>(base_priv(addr)); } // Convert specified PS3 address to a reference of specified (possibly converted to BE) type template inline to_be_t& _ref(u32 addr) { return *_ptr(addr); } template inline to_be_t& _ref_priv(u32 addr) { return *_ptr_priv(addr); } inline const be_t& read16(u32 addr) { return _ref(addr); } inline void write16(u32 addr, be_t value) { _ref(addr) = value; } inline const be_t& read32(u32 addr) { return _ref(addr); } inline void write32(u32 addr, be_t value) { _ref(addr) = value; } inline const be_t& read64(u32 addr) { return _ref(addr); } inline void write64(u32 addr, be_t value) { _ref(addr) = value; } void init(); } namespace psv { template inline to_le_t* _ptr(u32 addr) { return static_cast*>(base(addr)); } template inline to_le_t* _ptr_priv(u32 addr) { return static_cast*>(base_priv(addr)); } template inline to_le_t& _ref(u32 addr) { return *_ptr(addr); } template inline to_le_t& _ref_priv(u32 addr) { return *_ptr_priv(addr); } inline const le_t& read16(u32 addr) { return _ref(addr); } inline void write16(u32 addr, le_t value) { _ref(addr) = value; } inline const le_t& read32(u32 addr) { return _ref(addr); } inline void write32(u32 addr, le_t value) { _ref(addr) = value; } inline const le_t& read64(u32 addr) { return _ref(addr); } inline void write64(u32 addr, le_t value) { _ref(addr) = value; } void init(); } namespace psp { using namespace psv; void init(); } void close(); u32 stack_push(u32 size, u32 align_v); void stack_pop_verbose(u32 addr, u32 size) noexcept; extern thread_local u64 g_tls_fault_count; } #include "vm_var.h"