#include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/WindowsMappings.hpp" #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #endif namespace GuestArena { namespace { constexpr std::uintptr_t PreferredBase = 0x0000000200000000ull; constexpr std::uintptr_t SystemReservedStart = 0x00000007FFFFC000ull; constexpr std::uintptr_t SystemReservedEnd = 0x0000001000000000ull; constexpr std::size_t MaximumSize = 0x0000007000000000ull; constexpr std::size_t MinimumSize = 0x0000001000000000ull; constexpr std::uintptr_t MapAreaEnd = 0x000000FC00000000ull; std::uintptr_t alignUp(std::uintptr_t value, std::size_t alignment) { return (value + alignment - 1) & ~(static_cast(alignment) - 1); } class Arena { public: static Arena& Get() { static Arena arena; return arena; } bool Available() const { return _base != 0; } bool Contains(const void* pointer, std::size_t bytes) const { const auto address = reinterpret_cast(pointer); return _base != 0 && address >= _base && bytes <= _end - _base && address - _base <= (_end - _base) - bytes; } void* Allocate(std::size_t bytes, std::size_t alignment) { if (_base == 0) throw std::runtime_error("guest address space arena is unavailable"); if (alignment == 0 || (alignment & (alignment - 1)) != 0) throw std::invalid_argument("invalid guest arena alignment"); std::lock_guard lock(_lock); std::uintptr_t candidate = alignUp(_base, alignment); for (const auto& [start, end] : _used) { if (candidate + bytes <= start) break; candidate = std::max(candidate, alignUp(end, alignment)); } if (bytes > _end - candidate) throw std::runtime_error("guest address space arena exhausted"); _used.emplace(candidate, candidate + bytes); return reinterpret_cast(candidate); } void MarkUsed(const void* pointer, std::size_t bytes) { std::lock_guard lock(_lock); const auto start = reinterpret_cast(pointer); const auto next = _used.lower_bound(start); if ((next != _used.end() && next->first < start + bytes) || (next != _used.begin() && std::prev(next)->second > start)) throw std::runtime_error("fixed guest mapping overlaps a guest arena range"); _used.emplace(start, start + bytes); } void Release(const void* pointer, std::size_t bytes) { std::lock_guard lock(_lock); const auto start = reinterpret_cast(pointer); const auto end = start + bytes; auto it = _used.upper_bound(start); if (it != _used.begin()) --it; while (it != _used.end() && it->first < end) { const auto rangeStart = it->first; const auto rangeEnd = it->second; if (rangeEnd <= start) { ++it; continue; } it = _used.erase(it); if (rangeStart < start) _used.emplace(rangeStart, start); if (rangeEnd > end) _used.emplace(end, rangeEnd); } } private: Arena() { #ifdef _WIN32 // Windows places other reservations randomly, so take the lowest base and largest size that fit. for (std::uintptr_t base = PreferredBase; base + MinimumSize <= MapAreaEnd; base += MinimumSize) { for (std::size_t size = MaximumSize; size >= MinimumSize; size /= 2) { if (base + size > MapAreaEnd) continue; _writeWatched = std::getenv("APS5_NO_WRITE_WATCH") == nullptr; void* reserved = WindowsMappings::Get().Reserve(reinterpret_cast(base), size); if (!reserved) continue; _base = reinterpret_cast(reserved); _end = _base + size; if (_base < SystemReservedEnd && SystemReservedStart < _end) _used.emplace(std::max(_base, SystemReservedStart), std::min(_end, SystemReservedEnd)); return; } } std::fprintf(stderr, "[memory] guest arena unavailable below 0x%llx\n", static_cast(MapAreaEnd)); #endif } std::mutex _lock; std::map _used; std::uintptr_t _base = 0; std::uintptr_t _end = 0; bool _writeWatched = false; public: std::uintptr_t Base() const { return _base; } std::size_t Size() const { return _end - _base; } bool WriteWatched() const { return _writeWatched; } }; const bool g_reserved = (Arena::Get(), true); } bool GuestArenaAvailable_nid_postfix() { return Arena::Get().Available(); } bool GuestArenaContains_nid_postfix(const void* pointer, std::size_t bytes) { return Arena::Get().Contains(pointer, bytes); } void* GuestArenaAllocate_nid_postfix(std::size_t bytes, std::size_t alignment) { return Arena::Get().Allocate(bytes, alignment); } void GuestArenaMarkUsed_nid_postfix(const void* pointer, std::size_t bytes) { Arena::Get().MarkUsed(pointer, bytes); } void GuestArenaRelease_nid_postfix(const void* pointer, std::size_t bytes) { Arena::Get().Release(pointer, bytes); } void GuestArenaRange_nid_postfix(std::uintptr_t* base, std::size_t* bytes) { *base = Arena::Get().Base(); *bytes = Arena::Get().Size(); } #ifdef _WIN32 void GuestArenaSetProtection_nid_postfix(std::uintptr_t address, std::size_t bytes, std::uint32_t protection) { WindowsMappings::Get().SetProtection(address, bytes, protection); } bool GuestArenaHandleWrite_nid_postfix(std::uintptr_t address) { return WindowsMappings::Get().HandleWrite(address); } bool GuestArenaProtection_nid_postfix(std::uintptr_t address, std::uint32_t* protection) { return WindowsMappings::Get().Protection(address, protection); } bool GuestArenaCollectWrites_nid_postfix(std::uintptr_t address, std::size_t bytes, void** pages, std::size_t* count, bool clear) { return WindowsMappings::Get().Collect(address, bytes, pages, count, clear); } void GuestArenaCommit_nid_postfix(void* pointer, std::size_t bytes, std::uint32_t protection, std::size_t granule) { if (!Arena::Get().Contains(pointer, bytes)) throw std::invalid_argument("commit outside the guest arena"); WindowsMappings::Get().Commit(pointer, bytes, protection, granule, Arena::Get().WriteWatched()); } void GuestArenaReset_nid_postfix(void* pointer, std::size_t bytes) { if (!Arena::Get().Contains(pointer, bytes)) throw std::invalid_argument("reset outside the guest arena"); WindowsMappings::Get().Reset(pointer, bytes); } void GuestArenaMap_nid_postfix(void* pointer, std::size_t bytes, void* section, std::uint64_t offset, std::uint32_t protection) { if (!Arena::Get().Contains(pointer, bytes)) throw std::invalid_argument("shared mapping outside the guest arena"); WindowsMappings::Get().Map(pointer, bytes, section, offset, protection); } void* GuestArenaMapAlias_nid_postfix(std::uintptr_t address, std::size_t bytes) { return WindowsMappings::Get().MapAlias(address, bytes); } void GuestArenaUnmapAlias_nid_postfix(void* alias) { WindowsMappings::Get().UnmapAlias(alias); } #endif bool GuestArenaWriteWatched_nid_postfix() { return Arena::Get().WriteWatched(); } }