// Internal memory address-space helpers included by memory.cpp. // Kept in the same translation unit because they depend on memory.cpp-local helpers. #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS static constexpr uint64_t HOST_SYSTEM_MANAGED_MIN = 0x0000040000ull; static constexpr uint64_t HOST_SYSTEM_MANAGED_MAX = 0x07fffeffffull; static constexpr uint64_t HOST_USER_MIN = 0x1000000000ull; static constexpr uint64_t HOST_USER_MAX = 0xfbffffffffull; #endif class DirectMemoryBacking { public: enum class FailureReason { None, BackingUnavailable, PhysicalRangeOutOfBounds, HostAddressNotGranularityAligned, SizeNotGranularityAligned, ReserveAlignedFailed, FixedMapViewFailed, PlaceholderReserveFailed, PlaceholderMapFailed, ProtectFailed, ReservedContainerHasCommittedPages, ReservedContainerReleaseFailed, ReservedContainerRestoreFailed, ReservedContainerSplitNotGranularityAligned, WrongHostAddress, UnmapRollbackFailed, }; explicit DirectMemoryBacking(uint64_t size): m_size(size) { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS SYSTEM_INFO info {}; GetSystemInfo(&info); m_granularity = info.dwAllocationGranularity; m_handle = CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE, static_cast(m_size >> 32u), static_cast(m_size & 0xffffffffu), nullptr); if (m_handle == nullptr) { LOGF_COLOR(Log::Color::Red, "\t direct-memory backing: CreateFileMapping failed: 0x%08" PRIx32 "\n", static_cast(GetLastError())); return; } m_backing_base = static_cast( MapViewOfFile(m_handle, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, m_size)); if (m_backing_base == nullptr) { LOGF_COLOR(Log::Color::Red, "\t direct-memory backing: writable alias failed: 0x%08" PRIx32 "\n", static_cast(GetLastError())); CloseHandle(m_handle); m_handle = nullptr; } #elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX m_fd = static_cast(syscall(SYS_memfd_create, "KytyDirectMemory", 0)); if (m_fd < 0) { LOGF_COLOR(Log::Color::Red, "\t direct-memory backing: memfd_create failed: %d\n", errno); return; } if (ftruncate(m_fd, static_cast(m_size)) != 0) { LOGF_COLOR(Log::Color::Red, "\t direct-memory backing: ftruncate failed: %d\n", errno); close(m_fd); m_fd = -1; return; } auto* backing = mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0); if (backing == MAP_FAILED) { LOGF_COLOR(Log::Color::Red, "\t direct-memory backing: writable alias failed: %d\n", errno); close(m_fd); m_fd = -1; return; } m_backing_base = static_cast(backing); #endif } ~DirectMemoryBacking() { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS if (m_backing_base != nullptr) { UnmapViewOfFile(m_backing_base); } if (m_handle != nullptr) { CloseHandle(m_handle); } #elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX if (m_backing_base != nullptr) { munmap(m_backing_base, m_size); } if (m_fd >= 0) { close(m_fd); } #endif } KYTY_CLASS_NO_COPY(DirectMemoryBacking); static const char* GetFailureReasonName(FailureReason reason) { switch (reason) { case FailureReason::None: return "n/a"; case FailureReason::BackingUnavailable: return "backing-unavailable"; case FailureReason::PhysicalRangeOutOfBounds: return "physical-range-out-of-bounds"; case FailureReason::HostAddressNotGranularityAligned: return "host-address-not-64k-aligned"; case FailureReason::SizeNotGranularityAligned: return "size-not-64k-aligned"; case FailureReason::ReserveAlignedFailed: return "reserve-aligned-failed"; case FailureReason::FixedMapViewFailed: return "fixed-map-view-failed"; case FailureReason::PlaceholderReserveFailed: return "placeholder-reserve-failed"; case FailureReason::PlaceholderMapFailed: return "placeholder-map-failed"; case FailureReason::ProtectFailed: return "protect-failed"; case FailureReason::ReservedContainerHasCommittedPages: return "reserved-container-has-committed-pages"; case FailureReason::ReservedContainerReleaseFailed: return "reserved-container-release-failed"; case FailureReason::ReservedContainerRestoreFailed: return "reserved-container-restore-failed"; case FailureReason::ReservedContainerSplitNotGranularityAligned: return "reserved-container-split-not-64k-aligned"; case FailureReason::WrongHostAddress: return "wrong-host-address"; case FailureReason::UnmapRollbackFailed: return "unmap-rollback-failed"; default: return "unknown"; } } [[nodiscard]] bool IsAvailable() const { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS return m_handle != nullptr && m_backing_base != nullptr; #elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX return m_fd >= 0 && m_backing_base != nullptr; #else return false; #endif } bool TryWriteBacking(uint64_t vaddr, const void* data, uint64_t size) { if (data == nullptr) { return false; } const auto* source = static_cast(data); return TryTransferBacking(vaddr, size, [source](uint8_t* backing, uint64_t data_offset, size_t bytes) { std::memcpy(backing, source + data_offset, bytes); }); } bool TryReadBacking(uint64_t vaddr, void* data, uint64_t size) { if (data == nullptr) { return false; } auto* destination = static_cast(data); return TryTransferBacking( vaddr, size, [destination](uint8_t* backing, uint64_t data_offset, size_t bytes) { std::memcpy(destination + data_offset, backing, bytes); }); } uint64_t MapAligned(uint64_t search_addr, uint64_t size, uint64_t backing_offset, VirtualMemory::Mode mode, uint64_t alignment, FailureReason* failure_reason = nullptr) { if (!IsAvailable() || !RangeOk(backing_offset, size)) { SetFailure(failure_reason, !IsAvailable() ? FailureReason::BackingUnavailable : FailureReason::PhysicalRangeOutOfBounds); return 0; } if (alignment == 0) { alignment = 0x4000; } #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS alignment = std::max(alignment, m_granularity); return MapNewPlaceholderAligned(search_addr, size, backing_offset, mode, alignment, failure_reason); #else auto reserved = VirtualMemory::ReserveAligned(search_addr, size, alignment); if (reserved == 0) { SetFailure(failure_reason, FailureReason::ReserveAlignedFailed); return 0; } if (!ReleaseReservedProbe(reserved, failure_reason)) { return 0; } if (MapFixed(reserved, size, backing_offset, mode, failure_reason)) { return reserved; } return 0; #endif } bool MapFixed(uint64_t vaddr, uint64_t size, uint64_t backing_offset, VirtualMemory::Mode mode, FailureReason* failure_reason = nullptr) { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS if (ShouldChunkMap(size) && IsGranularityAligned(vaddr) && IsGranularityAligned(backing_offset)) { return MapFixedChunked(vaddr, size, backing_offset, mode, failure_reason); } #endif if (!IsAvailable() || !RangeOk(backing_offset, size)) { SetFailure(failure_reason, !IsAvailable() ? FailureReason::BackingUnavailable : FailureReason::PhysicalRangeOutOfBounds); return false; } void* mapped = MapView(vaddr, size, backing_offset, mode, failure_reason); #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS if (mapped == nullptr && ReleaseExactHostAllocation(vaddr, size)) { mapped = MapView(vaddr, size, backing_offset, mode, failure_reason); } if (mapped == nullptr) { ReleasedReservedHostAllocation released {}; if (ReleaseReservedHostAllocationForMap(vaddr, size, &released, failure_reason)) { mapped = MapView(vaddr, size, backing_offset, mode, failure_reason); if (mapped == nullptr || reinterpret_cast(mapped) != vaddr) { if (mapped != nullptr) { UnmapView(mapped, size); mapped = nullptr; } RestoreReleasedReservedHostAllocation(released); } else if (!ReserveReleasedReservedHostRemainders(released)) { UnmapView(mapped, size); mapped = nullptr; RestoreReleasedReservedHostAllocation(released); SetFailure(failure_reason, FailureReason::ReservedContainerRestoreFailed); } } } if (mapped == nullptr) { std::vector released_ranges; if (ReleaseReservedHostAllocationsForMap(vaddr, size, &released_ranges, failure_reason)) { mapped = MapView(vaddr, size, backing_offset, mode, failure_reason); if (mapped == nullptr || reinterpret_cast(mapped) != vaddr) { if (mapped != nullptr) { UnmapView(mapped, size); mapped = nullptr; } RestoreReleasedReservedHostAllocations(released_ranges); } else if (!ReserveReleasedReservedHostRemainders(released_ranges)) { UnmapView(mapped, size); mapped = nullptr; RestoreReleasedReservedHostAllocations(released_ranges); SetFailure(failure_reason, FailureReason::ReservedContainerRestoreFailed); } } } #endif if (mapped == nullptr || reinterpret_cast(mapped) != vaddr) { if (mapped != nullptr) { if (!UnmapView(mapped, size)) { SetFailure(failure_reason, FailureReason::UnmapRollbackFailed); } } if (mapped != nullptr) { SetFailure(failure_reason, FailureReason::WrongHostAddress); } return false; } { std::lock_guard lock(m_mutex); EXIT_IF(!m_maps.emplace(vaddr, MapEntry {vaddr, size, backing_offset, mode}).second); } SetFailure(failure_reason, FailureReason::None); return true; } bool MapExistingPlaceholderFixed(uint64_t vaddr, uint64_t size, uint64_t backing_offset, VirtualMemory::Mode mode, FailureReason* failure_reason = nullptr) { if (!IsAvailable() || !RangeOk(backing_offset, size)) { SetFailure(failure_reason, !IsAvailable() ? FailureReason::BackingUnavailable : FailureReason::PhysicalRangeOutOfBounds); return false; } #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS void* mapped = ReplacePlaceholderView(vaddr, size, backing_offset, mode, failure_reason); if (mapped == nullptr || reinterpret_cast(mapped) != vaddr) { if (mapped != nullptr) { UnmapView(mapped, size); SetFailure(failure_reason, FailureReason::WrongHostAddress); } return false; } { std::lock_guard lock(m_mutex); EXIT_IF(!m_maps.emplace(vaddr, MapEntry {vaddr, size, backing_offset, mode}).second); } SetFailure(failure_reason, FailureReason::None); return true; #else return MapFixed(vaddr, size, backing_offset, mode, failure_reason); #endif } bool Unmap(uint64_t vaddr, uint64_t size, bool preserve_placeholder = false, bool* placeholder_preserved = nullptr) { if (size == 0 || UINT64_MAX - vaddr < size) { return false; } if (placeholder_preserved != nullptr) { *placeholder_preserved = false; } if (!Contains(vaddr, size)) { return false; } const auto end = vaddr + size; auto current = vaddr; while (current < end) { MapEntry entry {}; { std::lock_guard lock(m_mutex); if (!FindContainingUnlocked(current, 1, &entry)) { return false; } } const auto entry_end = entry.vaddr + entry.size; const auto part_size = std::min(end, entry_end) - current; const bool preserve_part_placeholder = preserve_placeholder && current == vaddr && part_size == size; if (!UnmapOne(current, part_size, preserve_part_placeholder, placeholder_preserved)) { return false; } current += part_size; } return true; } bool UnmapOne(uint64_t vaddr, uint64_t size, bool preserve_placeholder = false, bool* placeholder_preserved = nullptr) { MapEntry old {}; { std::lock_guard lock(m_mutex); auto next = m_maps.upper_bound(vaddr); if (next != m_maps.begin()) { auto entry = std::prev(next); if (vaddr + size <= entry->second.vaddr + entry->second.size) { old = entry->second; m_maps.erase(entry); } } } if (old.size == 0) { return false; } const bool exact_unmap = (vaddr == old.vaddr && size == old.size); if (exact_unmap) { const bool unmapped = preserve_placeholder ? UnmapViewPreservePlaceholder(reinterpret_cast(old.vaddr), old.size) : UnmapView(reinterpret_cast(old.vaddr), old.size); if (!unmapped) { std::lock_guard lock(m_mutex); m_maps.emplace(old.vaddr, old); return false; } if (preserve_placeholder && placeholder_preserved != nullptr) { *placeholder_preserved = true; } return true; } const uint64_t left_size = vaddr - old.vaddr; const uint64_t right_addr = vaddr + size; const uint64_t old_end = old.vaddr + old.size; const uint64_t right_size = old_end - right_addr; #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS if (UnmapViewPreservePlaceholder(reinterpret_cast(old.vaddr), old.size)) { bool ok = true; if (left_size != 0 && left_size != old.size) { ok = SplitPlaceholder(old.vaddr, left_size) && ok; } if (right_size != 0) { ok = SplitPlaceholder(vaddr, size) && ok; } if (left_size != 0) { ok = MapExistingPlaceholderFixed(old.vaddr, left_size, old.backing_offset, old.mode) && ok; } if (right_size != 0) { ok = MapExistingPlaceholderFixed(right_addr, right_size, old.backing_offset + (right_addr - old.vaddr), old.mode) && ok; } if (ok) { if (placeholder_preserved != nullptr) { *placeholder_preserved = true; } return true; } } else #endif { if (!UnmapView(reinterpret_cast(old.vaddr), old.size)) { std::lock_guard lock(m_mutex); m_maps.emplace(old.vaddr, old); return false; } } bool ok = true; if (left_size != 0) { ok = MapFixed(old.vaddr, left_size, old.backing_offset, old.mode) && ok; } if (right_size != 0) { ok = MapFixed(right_addr, right_size, old.backing_offset + (right_addr - old.vaddr), old.mode) && ok; } if (!ok) { if (left_size != 0) { UnmapOne(old.vaddr, left_size); } if (right_size != 0) { UnmapOne(right_addr, right_size); } MapFixed(old.vaddr, old.size, old.backing_offset, old.mode); return false; } return ok; } void UpdateProtection(uint64_t vaddr, uint64_t size, VirtualMemory::Mode mode) { std::lock_guard lock(m_mutex); for (auto& map_entry: m_maps) { auto& entry = map_entry.second; if (VirtualRangesOverlap(vaddr, size, entry.vaddr, entry.size)) { entry.mode = mode; } } } bool Contains(uint64_t vaddr, uint64_t size) { if (size == 0 || UINT64_MAX - vaddr < size) { return false; } std::lock_guard lock(m_mutex); const auto end = vaddr + size; auto current = vaddr; while (current < end) { MapEntry entry {}; if (!FindContainingUnlocked(current, 1, &entry)) { return false; } const auto entry_end = entry.vaddr + entry.size; if (entry_end <= current) { return false; } current = std::min(end, entry_end); } return true; } bool SelfTest() { const uint64_t test_size = GetMappableTestSize(); if (test_size == 0 || !RangeOk(0, test_size)) { LOGF_COLOR(Log::Color::Yellow, "\t direct-memory backing self-test skipped\n"); return false; } const auto first = MapAligned(0, test_size, 0, VirtualMemory::Mode::ReadWrite, test_size); if (first == 0) { LOGF_COLOR(Log::Color::Yellow, "\t direct-memory backing self-test skipped: first alias unavailable\n"); return false; } const auto second = MapAligned(0, test_size, 0, VirtualMemory::Mode::ReadWrite, test_size); if (second == 0) { Unmap(first, test_size); LOGF_COLOR(Log::Color::Yellow, "\t direct-memory backing self-test skipped: second alias unavailable\n"); return false; } auto* second_ptr = reinterpret_cast(second); constexpr uint64_t test_value = 0x4b595459414c4941ull; // "KYTYALIA" EXIT_IF(!VirtualMemory::Protect(first, test_size, VirtualMemory::Mode::NoAccess)); const bool ok = TryWriteBacking(first, &test_value, sizeof(test_value)) && *second_ptr == test_value; const uint64_t clear = 0; EXIT_IF(!TryWriteBacking(first, &clear, sizeof(clear))); Unmap(second, test_size); Unmap(first, test_size); LOGF_COLOR(ok ? Log::Color::Green : Log::Color::Red, "\t direct-memory backing self-test: %s\n", ok ? "shared aliases ok" : "failed"); return ok; } private: template bool TryTransferBacking(uint64_t vaddr, uint64_t size, Transfer transfer) { if (!IsAvailable() || size == 0 || UINT64_MAX - vaddr < size) { return false; } std::lock_guard lock(m_mutex); const auto end = vaddr + size; auto current = vaddr; while (current < end) { MapEntry entry {}; if (!FindContainingUnlocked(current, 1, &entry)) { return false; } const auto bytes = std::min(end, entry.vaddr + entry.size) - current; if (!RangeOk(entry.backing_offset + current - entry.vaddr, bytes)) { return false; } current += bytes; } current = vaddr; while (current < end) { MapEntry entry {}; EXIT_IF(!FindContainingUnlocked(current, 1, &entry)); const auto bytes = std::min(end, entry.vaddr + entry.size) - current; const auto offset = entry.backing_offset + current - entry.vaddr; transfer(m_backing_base + offset, current - vaddr, static_cast(bytes)); current += bytes; } return true; } struct MapEntry { uint64_t vaddr = 0; uint64_t size = 0; uint64_t backing_offset = 0; VirtualMemory::Mode mode = VirtualMemory::Mode::NoAccess; }; bool FindContainingUnlocked(uint64_t vaddr, uint64_t size, MapEntry* out) const { EXIT_IF(out == nullptr); if (size == 0 || UINT64_MAX - vaddr < size) { return false; } auto next = m_maps.upper_bound(vaddr); if (next == m_maps.begin()) { return false; } const auto& entry = std::prev(next)->second; if (vaddr + size <= entry.vaddr + entry.size) { *out = entry; return true; } return false; } [[nodiscard]] bool RangeOk(uint64_t backing_offset, uint64_t size) const { return size != 0 && backing_offset < m_size && size <= m_size - backing_offset; } static void SetFailure(FailureReason* out, FailureReason reason) { if (out != nullptr) { *out = reason; } } #if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS static bool ReleaseReservedProbe(uint64_t vaddr, FailureReason* failure_reason) { if (VirtualMemory::Free(vaddr)) { return true; } SetFailure(failure_reason, FailureReason::ReservedContainerReleaseFailed); return false; } #endif #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS [[nodiscard]] uint64_t GetMappableTestSize() const { return m_granularity; } static constexpr uint64_t PageSize() { return 0x4000; } [[nodiscard]] static bool IsPageAligned(uint64_t value) { return (value & (PageSize() - 1u)) == 0; } [[nodiscard]] bool IsGranularityAligned(uint64_t value) const { return (value & (m_granularity - 1u)) == 0; } [[nodiscard]] uint64_t GetMapChunkSize() const { return std::max(m_granularity, 0x200000ull); } [[nodiscard]] bool ShouldChunkMap(uint64_t size) const { return size > GetMapChunkSize(); } uint64_t MapNewPlaceholderAligned(uint64_t search_addr, uint64_t size, uint64_t backing_offset, VirtualMemory::Mode mode, uint64_t alignment, FailureReason* failure_reason) { MEM_ADDRESS_REQUIREMENTS requirements {}; MEM_EXTENDED_PARAMETER parameter {}; const auto aligned_search = (search_addr + alignment - 1u) & ~(alignment - 1u); requirements.LowestStartingAddress = reinterpret_cast(search_addr != 0 ? aligned_search : HOST_SYSTEM_MANAGED_MIN); requirements.HighestEndingAddress = reinterpret_cast(search_addr != 0 ? HOST_USER_MAX : HOST_SYSTEM_MANAGED_MAX); requirements.Alignment = alignment; parameter.Type = MemExtendedParameterAddressRequirements; parameter.Pointer = &requirements; auto reserve = [&]() { return VirtualAlloc2(GetCurrentProcess(), nullptr, size, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, ¶meter, 1); }; auto* placeholder = reserve(); if (placeholder == nullptr && search_addr == 0) { requirements.LowestStartingAddress = reinterpret_cast(HOST_USER_MIN); requirements.HighestEndingAddress = reinterpret_cast(HOST_USER_MAX); placeholder = reserve(); } if (placeholder == nullptr) { SetFailure(failure_reason, FailureReason::PlaceholderReserveFailed); return 0; } const auto vaddr = reinterpret_cast(placeholder); std::vector entries; uint64_t mapped = 0; while (mapped < size) { const auto current = vaddr + mapped; const auto remaining = size - mapped; const auto part_size = std::min(GetMapChunkSize(), remaining); const bool split = part_size != remaining; if (split && !SplitPlaceholder(current, part_size)) { for (const auto& entry: entries) { UnmapView(reinterpret_cast(entry.vaddr), entry.size); } VirtualFree(reinterpret_cast(current), 0, MEM_RELEASE); SetFailure(failure_reason, FailureReason::PlaceholderReserveFailed); return 0; } if (ReplacePlaceholderView(current, part_size, backing_offset + mapped, mode, failure_reason) == nullptr) { for (const auto& entry: entries) { UnmapView(reinterpret_cast(entry.vaddr), entry.size); } VirtualFree(reinterpret_cast(current), 0, MEM_RELEASE); if (split) { VirtualFree(reinterpret_cast(current + part_size), 0, MEM_RELEASE); } return 0; } entries.push_back(MapEntry {current, part_size, backing_offset + mapped, mode}); mapped += part_size; } { std::lock_guard lock(m_mutex); for (const auto& entry: entries) { EXIT_IF(!m_maps.emplace(entry.vaddr, entry).second); } } SetFailure(failure_reason, FailureReason::None); return vaddr; } bool MapFixedChunked(uint64_t vaddr, uint64_t size, uint64_t backing_offset, VirtualMemory::Mode mode, FailureReason* failure_reason) { if (!IsAvailable() || !RangeOk(backing_offset, size)) { SetFailure(failure_reason, !IsAvailable() ? FailureReason::BackingUnavailable : FailureReason::PhysicalRangeOutOfBounds); return false; } if (!IsGranularityAligned(vaddr) || !IsGranularityAligned(backing_offset)) { SetFailure(failure_reason, FailureReason::HostAddressNotGranularityAligned); return false; } const auto chunk_size = GetMapChunkSize(); uint64_t mapped = 0; while (mapped < size) { const auto part_size = std::min(chunk_size, size - mapped); if (!MapFixed(vaddr + mapped, part_size, backing_offset + mapped, mode, failure_reason)) { uint64_t rollback = 0; while (rollback < mapped) { const auto rollback_size = std::min(chunk_size, mapped - rollback); UnmapOne(vaddr + rollback, rollback_size); rollback += rollback_size; } return false; } mapped += part_size; } SetFailure(failure_reason, FailureReason::None); return true; } static DWORD GetProtect(VirtualMemory::Mode mode) { switch (mode) { case VirtualMemory::Mode::Read: return PAGE_READONLY; case VirtualMemory::Mode::Write: case VirtualMemory::Mode::ReadWrite: return PAGE_READWRITE; case VirtualMemory::Mode::Execute: return PAGE_EXECUTE; case VirtualMemory::Mode::ExecuteRead: return PAGE_EXECUTE_READ; case VirtualMemory::Mode::ExecuteWrite: case VirtualMemory::Mode::ExecuteReadWrite: return PAGE_EXECUTE_READWRITE; case VirtualMemory::Mode::NoAccess: default: return PAGE_NOACCESS; } } static bool ApplyProtection(void* ptr, uint64_t size, VirtualMemory::Mode mode) { DWORD old_protect = 0; return VirtualProtect(ptr, size, GetProtect(mode), &old_protect) != 0; } static bool SplitPlaceholder(uint64_t vaddr, uint64_t size) { if (size == 0) { return true; } return VirtualFree(reinterpret_cast(vaddr), size, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER) != 0; } static bool ReleaseExactHostAllocation(uint64_t vaddr, uint64_t size) { MEMORY_BASIC_INFORMATION info {}; if (VirtualQuery(reinterpret_cast(vaddr), &info, sizeof(info)) == 0) { return false; } if ((info.State != MEM_RESERVE && info.State != MEM_COMMIT) || reinterpret_cast(info.BaseAddress) != vaddr || reinterpret_cast(info.AllocationBase) != vaddr || info.RegionSize != size) { return false; } return VirtualFree(reinterpret_cast(vaddr), 0, MEM_RELEASE) != 0; } struct ReleasedReservedHostAllocation { uint64_t base = 0; uint64_t size = 0; uint64_t map_start = 0; uint64_t map_size = 0; }; static bool ReserveHostRange(uint64_t vaddr, uint64_t size) { if (size == 0) { return true; } auto* ptr = VirtualAlloc(reinterpret_cast(vaddr), size, MEM_RESERVE, PAGE_NOACCESS); return ptr != nullptr && reinterpret_cast(ptr) == vaddr; } static bool RestoreReleasedReservedHostAllocation(const ReleasedReservedHostAllocation& released) { return ReserveHostRange(released.base, released.size); } static bool RestoreReleasedReservedHostAllocations( const std::vector& released_ranges) { bool ok = true; for (const auto& released: released_ranges) { ok = RestoreReleasedReservedHostAllocation(released) && ok; } return ok; } void* ReplacePlaceholderView(uint64_t vaddr, uint64_t size, uint64_t backing_offset, VirtualMemory::Mode mode, FailureReason* failure_reason) { const DWORD map_protect = mode == VirtualMemory::Mode::NoAccess ? PAGE_READWRITE : GetProtect(mode); void* ptr = MapViewOfFile3(m_handle, GetCurrentProcess(), reinterpret_cast(vaddr), backing_offset, size, MEM_REPLACE_PLACEHOLDER, map_protect, nullptr, 0); if (ptr == nullptr || reinterpret_cast(ptr) != vaddr) { if (ptr != nullptr) { UnmapViewOfFile(ptr); } SetFailure(failure_reason, FailureReason::PlaceholderMapFailed); return nullptr; } if (!ApplyProtection(ptr, size, mode)) { UnmapViewOfFile(ptr); SetFailure(failure_reason, FailureReason::ProtectFailed); return nullptr; } return ptr; } static bool ReserveReleasedReservedHostRemainders(const ReleasedReservedHostAllocation& released) { const auto map_end = released.map_start + released.map_size; const auto released_end = released.base + released.size; const auto left_size = released.map_start - released.base; const auto right_size = released_end - map_end; if (!ReserveHostRange(released.base, left_size)) { return false; } if (!ReserveHostRange(map_end, right_size)) { if (left_size != 0) { VirtualFree(reinterpret_cast(released.base), 0, MEM_RELEASE); } return false; } return true; } static bool ReserveReleasedReservedHostRemainders( const std::vector& released_ranges) { bool ok = true; for (const auto& released: released_ranges) { ok = ReserveReleasedReservedHostRemainders(released) && ok; } return ok; } bool QueryReservedHostAllocation(uint64_t vaddr, ReleasedReservedHostAllocation* out, FailureReason* failure_reason) const { EXIT_IF(out == nullptr); MEMORY_BASIC_INFORMATION info {}; if (VirtualQuery(reinterpret_cast(vaddr), &info, sizeof(info)) == 0) { SetFailure(failure_reason, FailureReason::PlaceholderReserveFailed); return false; } const auto allocation_base = reinterpret_cast(info.AllocationBase); const auto query_base = reinterpret_cast(info.BaseAddress); if (info.State != MEM_RESERVE || query_base > vaddr || allocation_base > vaddr) { SetFailure(failure_reason, FailureReason::PlaceholderReserveFailed); return false; } uint64_t allocation_end = query_base + info.RegionSize; uint64_t scan = allocation_end; while (true) { MEMORY_BASIC_INFORMATION next {}; if (VirtualQuery(reinterpret_cast(scan), &next, sizeof(next)) == 0 || reinterpret_cast(next.AllocationBase) != allocation_base || next.State == MEM_FREE) { break; } if (next.State != MEM_RESERVE) { SetFailure(failure_reason, FailureReason::ReservedContainerHasCommittedPages); return false; } allocation_end = reinterpret_cast(next.BaseAddress) + next.RegionSize; scan = allocation_end; } scan = allocation_base; while (scan < allocation_end) { MEMORY_BASIC_INFORMATION part {}; if (VirtualQuery(reinterpret_cast(scan), &part, sizeof(part)) == 0 || reinterpret_cast(part.AllocationBase) != allocation_base || part.State != MEM_RESERVE) { SetFailure(failure_reason, FailureReason::ReservedContainerHasCommittedPages); return false; } scan = reinterpret_cast(part.BaseAddress) + part.RegionSize; } out->base = allocation_base; out->size = allocation_end - allocation_base; out->map_start = 0; out->map_size = 0; return true; } bool ReleaseReservedHostAllocationForMap(uint64_t vaddr, uint64_t size, ReleasedReservedHostAllocation* out, FailureReason* failure_reason) const { if (!QueryReservedHostAllocation(vaddr, out, failure_reason)) { return false; } const auto allocation_end = out->base + out->size; if (vaddr < out->base || vaddr + size > allocation_end) { SetFailure(failure_reason, FailureReason::PlaceholderReserveFailed); return false; } if (out->base == vaddr && out->size == size) { return false; } if (!IsGranularityAligned(size)) { SetFailure(failure_reason, FailureReason::ReservedContainerSplitNotGranularityAligned); return false; } if (VirtualFree(reinterpret_cast(out->base), 0, MEM_RELEASE) == 0) { SetFailure(failure_reason, FailureReason::ReservedContainerReleaseFailed); return false; } out->map_start = vaddr; out->map_size = size; return true; } bool ReleaseReservedHostAllocationsForMap(uint64_t vaddr, uint64_t size, std::vector* out, FailureReason* failure_reason) const { EXIT_IF(out == nullptr); out->clear(); if (size == 0 || !IsPageAligned(vaddr) || !IsPageAligned(size)) { SetFailure(failure_reason, FailureReason::ReservedContainerSplitNotGranularityAligned); return false; } const auto target_end = vaddr + size; auto current = vaddr; while (current < target_end) { ReleasedReservedHostAllocation released {}; if (!QueryReservedHostAllocation(current, &released, failure_reason)) { out->clear(); return false; } const auto map_start = std::max(vaddr, released.base); const auto map_end = std::min(target_end, released.base + released.size); if (map_start >= map_end || !IsPageAligned(map_start) || !IsPageAligned(map_end - map_start)) { SetFailure(failure_reason, FailureReason::ReservedContainerSplitNotGranularityAligned); out->clear(); return false; } released.map_start = map_start; released.map_size = map_end - map_start; out->push_back(released); current = map_end; } for (size_t i = 0; i < out->size(); i++) { const auto& released = (*out)[i]; if (VirtualFree(reinterpret_cast(released.base), 0, MEM_RELEASE) == 0) { for (size_t restore_index = 0; restore_index < i; restore_index++) { RestoreReleasedReservedHostAllocation((*out)[restore_index]); } SetFailure(failure_reason, FailureReason::ReservedContainerReleaseFailed); out->clear(); return false; } } return !out->empty(); } void* MapPlaceholderView(uint64_t vaddr, uint64_t size, uint64_t backing_offset, VirtualMemory::Mode mode, FailureReason* failure_reason) { void* placeholder = VirtualAlloc2(GetCurrentProcess(), reinterpret_cast(vaddr), size, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, nullptr, 0); if (placeholder == nullptr || reinterpret_cast(placeholder) != vaddr) { if (placeholder != nullptr) { VirtualFree(placeholder, 0, MEM_RELEASE); } SetFailure(failure_reason, FailureReason::PlaceholderReserveFailed); return nullptr; } auto* ptr = ReplacePlaceholderView(vaddr, size, backing_offset, mode, failure_reason); if (ptr == nullptr) { VirtualFree(reinterpret_cast(vaddr), 0, MEM_RELEASE); return nullptr; } return ptr; } void* MapView(uint64_t vaddr, uint64_t size, uint64_t backing_offset, VirtualMemory::Mode mode, FailureReason* failure_reason) { constexpr DWORD access = FILE_MAP_READ | FILE_MAP_WRITE | FILE_MAP_EXECUTE; void* ptr = MapViewOfFileEx(m_handle, access, static_cast(backing_offset >> 32u), static_cast(backing_offset & 0xffffffffu), size, reinterpret_cast(vaddr)); if (ptr == nullptr) { SetFailure(failure_reason, FailureReason::FixedMapViewFailed); ptr = ReplacePlaceholderView(vaddr, size, backing_offset, mode, failure_reason); if (ptr != nullptr) { return ptr; } return MapPlaceholderView(vaddr, size, backing_offset, mode, failure_reason); } if (!ApplyProtection(ptr, size, mode)) { UnmapViewOfFile(ptr); SetFailure(failure_reason, FailureReason::ProtectFailed); return nullptr; } return ptr; } static bool UnmapView(void* vaddr, uint64_t /*size*/) { return UnmapViewOfFile(vaddr) != 0; } static bool UnmapViewPreservePlaceholder(void* vaddr, uint64_t /*size*/) { return UnmapViewOfFile2(GetCurrentProcess(), vaddr, MEM_PRESERVE_PLACEHOLDER) != 0; } HANDLE m_handle = nullptr; uint64_t m_granularity = 0x10000; #elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX [[nodiscard]] static uint64_t GetMappableTestSize() { return 0x4000; } static int GetProtect(VirtualMemory::Mode mode) { switch (mode) { case VirtualMemory::Mode::Read: return PROT_READ; case VirtualMemory::Mode::Write: case VirtualMemory::Mode::ReadWrite: return PROT_READ | PROT_WRITE; case VirtualMemory::Mode::Execute: return PROT_EXEC; case VirtualMemory::Mode::ExecuteRead: return PROT_EXEC | PROT_READ; case VirtualMemory::Mode::ExecuteWrite: case VirtualMemory::Mode::ExecuteReadWrite: return PROT_EXEC | PROT_READ | PROT_WRITE; case VirtualMemory::Mode::NoAccess: default: return PROT_NONE; } } void* MapView(uint64_t vaddr, uint64_t size, uint64_t backing_offset, VirtualMemory::Mode mode, FailureReason* failure_reason) { void* ptr = mmap(reinterpret_cast(vaddr), size, GetProtect(mode), MAP_SHARED | MAP_FIXED, m_fd, static_cast(backing_offset)); if (ptr == MAP_FAILED) { SetFailure(failure_reason, FailureReason::FixedMapViewFailed); return nullptr; } return ptr; } static bool UnmapView(void* vaddr, uint64_t size) { return munmap(vaddr, size) == 0; } static bool UnmapViewPreservePlaceholder(void* /*vaddr*/, uint64_t /*size*/) { return false; } int m_fd = -1; #endif uint8_t* m_backing_base = nullptr; uint64_t m_size = 0; std::mutex m_mutex; std::map m_maps; }; class PlaceholderAddressSpace { public: PlaceholderAddressSpace() { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS SYSTEM_INFO info {}; GetSystemInfo(&info); m_granularity = info.dwAllocationGranularity; #endif } KYTY_CLASS_NO_COPY(PlaceholderAddressSpace); [[nodiscard]] uint64_t GetGranularity() const { return m_granularity; } uint64_t ReserveAligned(uint64_t search_addr, uint64_t size, uint64_t alignment) { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS if (size == 0) { return 0; } alignment = std::max(alignment != 0 ? alignment : 0x4000, m_granularity); MEM_ADDRESS_REQUIREMENTS req {}; MEM_EXTENDED_PARAMETER param {}; req.LowestStartingAddress = reinterpret_cast( AlignUp(search_addr != 0 ? search_addr : HOST_SYSTEM_MANAGED_MIN, alignment)); req.HighestEndingAddress = reinterpret_cast(search_addr != 0 ? HOST_USER_MAX : HOST_SYSTEM_MANAGED_MAX); req.Alignment = alignment; param.Type = MemExtendedParameterAddressRequirements; param.Pointer = &req; auto* ptr = VirtualAlloc2(GetCurrentProcess(), nullptr, size, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, ¶m, 1); if (ptr == nullptr && search_addr == 0) { req.LowestStartingAddress = reinterpret_cast(HOST_USER_MIN); req.HighestEndingAddress = reinterpret_cast(HOST_USER_MAX); ptr = VirtualAlloc2(GetCurrentProcess(), nullptr, size, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, ¶m, 1); } if (ptr == nullptr) { return 0; } const auto addr = reinterpret_cast(ptr); AddFree(addr, size); return addr; #else auto addr = VirtualMemory::ReserveAligned(search_addr, size, alignment); return addr; #endif } bool ReserveFixed(uint64_t vaddr, uint64_t size) { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS if (vaddr == 0 || size == 0 || !IsPageAligned(vaddr) || !IsPageAligned(size)) { return false; } { std::lock_guard lock(m_mutex); if (FindContaining(vaddr, size) != m_free.end()) { return true; } } auto* ptr = VirtualAlloc2(GetCurrentProcess(), reinterpret_cast(vaddr), size, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, nullptr, 0); if (ptr == nullptr || reinterpret_cast(ptr) != vaddr) { if (ptr != nullptr) { VirtualFree(ptr, 0, MEM_RELEASE); } return false; } AddFree(vaddr, size); return true; #else return VirtualMemory::ReserveFixed(vaddr, size); #endif } bool Consume(uint64_t vaddr, uint64_t size) { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS std::lock_guard lock(m_mutex); auto it = FindContaining(vaddr, size); if (it == m_free.end()) { return false; } if (!SplitUnlocked(it, vaddr, size)) { return false; } it = m_free.find(vaddr); if (it == m_free.end() || it->second != size) { return false; } m_free.erase(it); return true; #else return false; #endif } #if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS) bool TestContainsFree(uint64_t vaddr, uint64_t size) { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS std::lock_guard lock(m_mutex); return FindContaining(vaddr, size) != m_free.end(); #else return false; #endif } #endif bool ReleaseFree(uint64_t vaddr, uint64_t size) { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS std::lock_guard lock(m_mutex); auto it = FindContaining(vaddr, size); if (it == m_free.end() || !SplitUnlocked(it, vaddr, size)) { return false; } it = m_free.find(vaddr); if (it == m_free.end() || it->second != size) { return false; } m_free.erase(it); if (VirtualFree(reinterpret_cast(vaddr), 0, MEM_RELEASE) != 0) { return true; } AddFreeUnlocked(vaddr, size); return false; #else return VirtualMemory::FreeRange(vaddr, size); #endif } bool Commit(uint64_t vaddr, uint64_t size, VirtualMemory::Mode mode) { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS if (!Consume(vaddr, size)) { return false; } auto* ptr = VirtualAlloc2(GetCurrentProcess(), reinterpret_cast(vaddr), size, MEM_RESERVE | MEM_COMMIT | MEM_REPLACE_PLACEHOLDER, GetProtect(mode), nullptr, 0); if (ptr == nullptr || reinterpret_cast(ptr) != vaddr) { if (ptr != nullptr) { VirtualFree(ptr, 0, MEM_RELEASE); } AddFree(vaddr, size); return false; } return true; #else return false; #endif } bool ReleaseCommitted(uint64_t vaddr, uint64_t size) { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS if (vaddr == 0 || size == 0 || !IsPageAligned(vaddr) || !IsPageAligned(size)) { return false; } if (VirtualFree(reinterpret_cast(vaddr), size, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER) == 0) { return false; } AddFree(vaddr, size); return true; #else return false; #endif } bool SelfTest() { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS const auto addr = ReserveAligned(0, m_granularity, m_granularity); if (addr == 0) { LOGF_COLOR(Log::Color::Yellow, "\t placeholder address-space self-test skipped: reserve unavailable\n"); return false; } if (!Commit(addr, m_granularity, VirtualMemory::Mode::ReadWrite)) { ReleaseFree(addr, m_granularity); LOGF_COLOR(Log::Color::Red, "\t placeholder address-space self-test: commit failed\n"); return false; } auto* ptr = reinterpret_cast(addr); *ptr = 0x4b59545950484f4cull; // "KYTYPHOL" if (!ReleaseCommitted(addr, m_granularity)) { VirtualFree(reinterpret_cast(addr), 0, MEM_RELEASE); LOGF_COLOR(Log::Color::Red, "\t placeholder address-space self-test: release-committed failed\n"); return false; } const bool ok = ReleaseFree(addr, m_granularity); LOGF_COLOR(ok ? Log::Color::Green : Log::Color::Red, "\t placeholder address-space self-test: %s\n", ok ? "ok" : "release-free failed"); return ok; #else LOGF_COLOR(Log::Color::Yellow, "\t placeholder address-space self-test skipped\n"); return false; #endif } void AddFree(uint64_t vaddr, uint64_t size) { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS std::lock_guard lock(m_mutex); AddFreeUnlocked(vaddr, size); #endif } private: #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS static constexpr uint64_t PageSize() { return 0x4000; } static uint64_t AlignUp(uint64_t value, uint64_t alignment) { return (value + alignment - 1u) & ~(alignment - 1u); } [[nodiscard]] static bool IsPageAligned(uint64_t value) { return (value & (PageSize() - 1u)) == 0; } [[nodiscard]] bool IsGranularityAligned(uint64_t value) const { return (value & (m_granularity - 1u)) == 0; } static DWORD GetProtect(VirtualMemory::Mode mode) { switch (mode) { case VirtualMemory::Mode::Read: return PAGE_READONLY; case VirtualMemory::Mode::Write: case VirtualMemory::Mode::ReadWrite: return PAGE_READWRITE; case VirtualMemory::Mode::Execute: return PAGE_EXECUTE; case VirtualMemory::Mode::ExecuteRead: return PAGE_EXECUTE_READ; case VirtualMemory::Mode::ExecuteWrite: case VirtualMemory::Mode::ExecuteReadWrite: return PAGE_EXECUTE_READWRITE; case VirtualMemory::Mode::NoAccess: default: return PAGE_NOACCESS; } } std::map::iterator FindContaining(uint64_t vaddr, uint64_t size) { auto next = m_free.upper_bound(vaddr); if (next == m_free.begin()) { return m_free.end(); } auto it = std::prev(next); auto end = it->first + it->second; return (vaddr >= it->first && vaddr + size <= end ? it : m_free.end()); } bool SplitUnlocked(std::map::iterator it, uint64_t vaddr, uint64_t size) { const auto base = it->first; const auto end = it->first + it->second; if (base == vaddr && end == vaddr + size) { return true; } if (base != vaddr) { const auto left_size = vaddr - base; if (VirtualFree(reinterpret_cast(base), left_size, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER) == 0) { return false; } it->second = left_size; it = m_free.emplace(vaddr, end - vaddr).first; } if (it->second != size) { if (VirtualFree(reinterpret_cast(it->first), size, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER) == 0) { return false; } const auto right_start = it->first + size; const auto right_size = it->second - size; it->second = size; m_free.emplace(right_start, right_size); } return true; } void AddFreeUnlocked(uint64_t vaddr, uint64_t size) { if (vaddr == 0 || size == 0) { return; } auto [it, inserted] = m_free.emplace(vaddr, size); if (!inserted) { it->second = size; } auto base = it->first; auto end = it->first + it->second; auto first = it; while (first != m_free.begin()) { auto prev = std::prev(first); if (prev->first + prev->second != base) { break; } base = prev->first; first = prev; } auto last = std::next(it); while (last != m_free.end() && end == last->first) { end = last->first + last->second; last = std::next(last); } if (base != it->first || end != it->first + it->second) { if (VirtualFree(reinterpret_cast(base), end - base, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS) != 0) { m_free.erase(first, last); m_free.emplace(base, end - base); } } } std::mutex m_mutex; std::map m_free; #endif uint64_t m_granularity = 0x10000; };