#pragma once #include #include #include "util/types.hpp" #include "util/atomic.hpp" #include "util/auto_typemap.hpp" #include "Utilities/StrFmt.h" #include "util/to_endian.hpp" namespace utils { class shm; class address_range; } namespace vm { extern u8* const g_base_addr; extern u8* const g_sudo_addr; extern u8* const g_exec_addr; extern u8* const g_stat_addr; extern u8* const g_free_addr; extern u8 g_reservations[65536 / 128 * 64]; struct writer_lock; enum memory_location_t : uint { main, user64k, user1m, rsx_context, video, stack, spu, 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_64k_size = (1 << 5), page_1m_size = (1 << 6), page_allocated = (1 << 7), }; // Address type enum addr_t : u32 {}; // Page information using memory_page = atomic_t; // 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 flags for specified memory range (unsafe) bool check_addr(u32 addr, u8 flags, u32 size); template bool check_addr(u32 addr, u8 flags = page_readable) { extern std::array g_pages; if (Size - 1 >= 4095u || Size & (Size - 1) || addr % Size) { // TODO return check_addr(addr, flags, Size); } return !(~g_pages[addr / 4096] & (flags | page_allocated)); } // Read string in a safe manner (page aware) (bool true = if null-termination) bool read_string(u32 addr, u32 max_size, std::string& out_string, bool check_pages = true) noexcept; // Search and map memory in specified memory location (min alignment is 0x10000) u32 alloc(u32 size, memory_location_t location, u32 align = 0x10000); // Map memory at specified address (in optionally specified memory location) bool falloc(u32 addr, u32 size, memory_location_t location = any, const std::shared_ptr* src = nullptr); // Unmap memory at specified address (in optionally specified memory location), return size u32 dealloc(u32 addr, memory_location_t location = any, const std::shared_ptr* src = nullptr); // utils::memory_lock wrapper for locking sudo memory void lock_sudo(u32 addr, u32 size); enum block_flags_3 { page_size_4k = 0x100, // SYS_MEMORY_PAGE_SIZE_4K page_size_64k = 0x200, // SYS_MEMORY_PAGE_SIZE_64K page_size_1m = 0x400, // SYS_MEMORY_PAGE_SIZE_1M page_size_mask = 0xF00, // SYS_MEMORY_PAGE_SIZE_MASK stack_guarded = 0x10, preallocated = 0x20, // nonshareable bf0_0x1 = 0x1, // TODO: document bf0_0x2 = 0x2, // TODO: document bf0_mask = bf0_0x1 | bf0_0x2, }; enum alloc_flags { alloc_hidden = 0x1000, alloc_unwritable = 0x2000, alloc_executable = 0x4000, alloc_prot_mask = alloc_hidden | alloc_unwritable | alloc_executable, }; // Object that handles memory allocations inside specific constant bounds ("location") class block_t final { auto_typemap m; // Common mapped region for special cases std::shared_ptr m_common; atomic_t m_id = 0; bool try_alloc(u32 addr, u64 bflags, u32 size, std::shared_ptr&&) const; // Unmap block bool unmap(); friend bool _unmap_block(const std::shared_ptr&); public: block_t(u32 addr, u32 size, u64 flags); ~block_t(); public: const u32 addr; // Start address const u32 size; // Total size const u64 flags; // Byte 0xF000: block_flags_3 // Byte 0x0F00: block_flags_2_page_size (SYS_MEMORY_PAGE_SIZE_*) // Byte 0x00F0: block_flags_1 // Byte 0x000F: block_flags_0 // Search and map memory (min alignment is 0x10000) u32 alloc(u32 size, const std::shared_ptr* = nullptr, u32 align = 0x10000, u64 flags = 0); // Try to map memory at fixed location bool falloc(u32 addr, u32 size, const std::shared_ptr* = nullptr, u64 flags = 0); // Unmap memory at specified location previously returned by alloc(), return size u32 dealloc(u32 addr, const std::shared_ptr* = nullptr) const; // Get memory at specified address (if size = 0, addr assumed exact) std::pair> peek(u32 addr, u32 size = 0) const; // Get allocated memory count u32 used(); // Internal u32 imp_used(const vm::writer_lock&) const; // Returns 0 if invalid, none-zero unique id if valid u64 is_valid() const { return m_id; } // Serialization helper for shared memory void get_shared_memory(std::vector>& shared); // Returns sample address for shared memory, 0 on failure u32 get_shm_addr(const std::shared_ptr& shared); // Serialization void save(utils::serial& ar, std::map& shared); block_t(utils::serial& ar, std::vector>& shared); }; // Create new memory block with specified parameters and return it std::shared_ptr map(u32 addr, u32 size, u64 flags = 0); // Create new memory block with at arbitrary position with specified alignment std::shared_ptr find_map(u32 size, u32 align, u64 flags = 0); // Delete existing memory block with specified start address, .first=its ptr, .second=success std::pair, bool> unmap(u32 addr, bool must_be_empty = false, const std::shared_ptr* ptr = nullptr); // Get memory block associated with optionally specified memory location or optionally specified address std::shared_ptr get(memory_location_t location, u32 addr = 0); // Allocate segment at specified location, does nothing if exists already std::shared_ptr reserve_map(memory_location_t location, u32 addr, u32 area_size, u64 flags = page_size_64k); // Get PS3 virtual memory address from the provided pointer (nullptr or pointer from outside is always converted to 0) // Super memory is allowed as well inline std::pair try_get_addr(const void* real_ptr) { const std::make_unsigned_t diff = static_cast(real_ptr) - g_base_addr; if (diff <= u64{u32{umax}} * 2 + 1) { return {vm::addr_t{static_cast(diff)}, true}; } return {}; } // Unsafe convert host ptr to PS3 VM address (clamp with 4GiB alignment assumption) inline vm::addr_t get_addr(const void* ptr) { return vm::addr_t{static_cast(uptr(ptr))}; } template requires (std::is_integral_v && (sizeof(+T{}) > 4 || std::is_signed_v)) vm::addr_t cast(const T& addr, u32 line = __builtin_LINE(), u32 col = __builtin_COLUMN(), const char* file = __builtin_FILE(), const char* func = __builtin_FUNCTION()) { return vm::addr_t{::narrow(+addr, line, col, file, func)}; } template requires (std::is_integral_v && (sizeof(+T{}) <= 4 && !std::is_signed_v)) vm::addr_t cast(const T& addr, u32 = 0, u32 = 0, const char* = nullptr, const char* = nullptr) { return vm::addr_t{static_cast(+addr)}; } // Convert specified PS3/PSV virtual memory address to a pointer for common access template requires (std::is_integral_v) inline void* base(T addr) { return g_base_addr + static_cast(vm::cast(addr)); } inline const u8& read8(u32 addr) { return g_base_addr[addr]; } inline void write8(u32 addr, u8 value) { g_base_addr[addr] = value; } // Read or write virtual memory in a safe manner, returns false on failure bool try_access(u32 addr, void* ptr, u32 size, bool is_write); inline namespace ps3_ { // Convert specified PS3 address to a pointer of specified (possibly converted to BE) type template inline to_be_t* _ptr(const U& addr) { return static_cast*>(base(addr)); } // Convert specified PS3 address to a reference of specified (possibly converted to BE) type template inline to_be_t& _ref(const U& addr) { return *static_cast*>(base(addr)); } // Access memory bypassing memory protection template inline to_be_t* get_super_ptr(u32 addr) { return reinterpret_cast*>(g_sudo_addr + 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(); } void close(); void load(utils::serial& ar); void save(utils::serial& ar); // Returns sample address for shared memory, 0 on failure (wraps block_t::get_shm_addr) u32 get_shm_addr(const std::shared_ptr& shared); template class _ptr_base; template class _ref_base; }