Files
AnyPS5/core/libs/prx/libc/include/WindowsMappings.hpp
T
Angel21PC 2d1cc82bce fix(libc): alias non-contiguous shared guest views on Windows
On Windows, a host import of shared guest memory goes through a
read-write alias from WindowsMappings::MapAlias. The alias was a single
MapViewOfFile3 of one section, so MapAlias threw when the views of the
range were not one contiguous run of that section. One
sceKernelMapDirectMemory over two adjacent direct memory allocations
gives such a range, as each allocation has its own section, and so do
pages remapped in place to another physical order.

Such ranges are now aliased run by run: a placeholder reservation is
split at each run of consecutive pages of one section, and each run
maps that section at its offset. UnmapAlias unmaps every run and forgets
the alias even when one unmap fails. A contiguous range keeps the single
view, and a range with a page that is not a shared view is still
refused.

guest_memory maps two adjacent direct memory allocations of two pages
and one page with one call, and two pages in reverse physical order.
It reads and writes every page through the alias. The first test also
checks that the alias has one view per run and that unmapping it frees
its pages. A range whose second page is flexible memory is still
refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit fc6868d8b6)
2026-10-13 07:31:17 +00:00

609 lines
30 KiB
C++

#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_WINDOWSMAPPINGS_HPP
#define CORE_LIBS_PRX_LIBC_INCLUDE_WINDOWSMAPPINGS_HPP
#ifdef _WIN32
#include <windows.h>
#include <algorithm>
#include <array>
#include <cstdio>
#include <cstddef>
#include <cstdint>
#include <mutex>
#include <map>
#include <memory>
#include <vector>
#include <stdexcept>
#include <string>
#include <system_error>
namespace GuestArena {
class WindowsMappings {
public:
enum class Collection { All, Armed, Fresh };
static WindowsMappings& Get() {
static WindowsMappings mappings;
return mappings;
}
void* Reserve(void* address, std::size_t bytes) {
return allocate(GetCurrentProcess(), address, bytes, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, nullptr, 0);
}
std::vector<std::pair<std::uintptr_t, std::size_t>> Commit(void* address, std::size_t bytes, DWORD protection, std::size_t granule, bool watched) {
std::vector<std::pair<std::uintptr_t, std::size_t>> created;
std::lock_guard lock(mutex);
const auto end = reinterpret_cast<std::uintptr_t>(address) + bytes;
for (auto cursor = reinterpret_cast<std::uintptr_t>(address); cursor < end;) {
const auto memory = query(cursor);
const auto stop = std::min(end, reinterpret_cast<std::uintptr_t>(memory.BaseAddress) + memory.RegionSize);
if (memory.State == MEM_RESERVE) {
const auto limit = std::min(end, cursor + granule);
auto placeholderEnd = stop;
while (placeholderEnd < limit) {
const auto next = query(placeholderEnd);
if (next.State != MEM_RESERVE) break;
placeholderEnd = reinterpret_cast<std::uintptr_t>(next.BaseAddress) + next.RegionSize;
}
const auto size = std::min(limit, placeholderEnd) - cursor;
reset(cursor, size);
const DWORD flags = MEM_RESERVE | MEM_COMMIT | MEM_REPLACE_PLACEHOLDER | (watched ? MEM_WRITE_WATCH : 0);
if (!allocate(GetCurrentProcess(), reinterpret_cast<void*>(cursor), size, flags, protection, nullptr, 0)) fail("replace guest placeholder with private memory");
if (!created.empty() && created.back().first + created.back().second == cursor) created.back().second += size;
else created.emplace_back(cursor, size);
cursor += size;
} else {
if (memory.State != MEM_COMMIT) throw std::runtime_error("guest memory is not committed");
const auto mapped = views.find(cursor & ~(pageBytes - 1));
if (mapped != views.end()) {
mapped->second.protection = protection;
mapped->second.armed = 0;
invalidate(*mapped->second.page);
}
DWORD previous;
if (!VirtualProtect(reinterpret_cast<void*>(cursor), stop - cursor, protection, &previous)) fail("protect guest memory");
cursor = stop;
}
}
return created;
}
void Reset(void* address, std::size_t bytes) {
std::lock_guard lock(mutex);
reset(reinterpret_cast<std::uintptr_t>(address), bytes);
}
void Map(void* address, std::size_t bytes, HANDLE section, std::uint64_t offset, DWORD protection) {
std::lock_guard lock(mutex);
auto cursor = reinterpret_cast<std::uintptr_t>(address);
reset(cursor, bytes);
HANDLE duplicate = nullptr;
if (!DuplicateHandle(GetCurrentProcess(), section, GetCurrentProcess(), &duplicate, 0, FALSE, DUPLICATE_SAME_ACCESS)) fail("keep shared guest section");
const auto owned = std::make_shared<Section>(duplicate);
for (std::size_t done = 0; done < bytes; done += pageBytes) {
split(cursor + done, pageBytes);
void* page = reinterpret_cast<void*>(cursor + done);
if (!map(section, GetCurrentProcess(), page, offset + done, pageBytes, MEM_REPLACE_PLACEHOLDER, PAGE_EXECUTE_READWRITE, nullptr, 0)) fail("map shared guest page");
MEMORY_BASIC_INFORMATION memory{};
if (VirtualQuery(page, &memory, sizeof(memory)) != sizeof(memory)) fail("query shared guest page");
if (memory.State == MEM_RESERVE && !VirtualAlloc(page, pageBytes, MEM_COMMIT, PAGE_EXECUTE_READWRITE)) fail("commit shared guest page");
DWORD previous;
if (!VirtualProtect(page, pageBytes, protection, &previous)) fail("protect shared guest page");
const auto key = std::make_pair(reinterpret_cast<std::uintptr_t>(section), offset + done);
auto shared = physical[key].lock();
if (!shared) {
shared = std::make_shared<SharedPage>();
physical[key] = shared;
}
const auto base = cursor + done;
shared->aliases.push_back(base);
views.emplace(base, View{shared, protection, {}, 0, owned, offset + done, 0});
remember(freshRanges, base, base + pageBytes);
invalidate(*shared);
}
}
void SetProtection(std::uintptr_t address, std::size_t bytes, DWORD protection) {
std::lock_guard lock(mutex);
for (auto it = views.lower_bound(address); it != views.end() && it->first < address + bytes; ++it) {
it->second.protection = protection;
it->second.armed = 0;
invalidate(*it->second.page);
}
}
void Pin(std::uintptr_t address, std::size_t bytes) {
std::lock_guard lock(mutex);
for (auto it = views.lower_bound(address & ~(pageBytes - 1)); it != views.end() && it->first < address + bytes; ++it) {
auto& page = *it->second.page;
++page.pins;
for (const auto alias : page.aliases) {
auto& view = views.at(alias);
if (view.armed == 0) continue;
protectSubpages(alias, view.armed, view.protection, "pin shared guest page writable");
view.armed = 0;
}
invalidate(page);
}
}
void Unpin(std::uintptr_t address, std::size_t bytes) {
std::lock_guard lock(mutex);
for (auto it = views.lower_bound(address & ~(pageBytes - 1)); it != views.end() && it->first < address + bytes; ++it) {
auto& page = *it->second.page;
if (page.pins != 0) --page.pins;
invalidate(page);
}
}
bool HandleWrite(std::uintptr_t address) {
std::lock_guard lock(mutex);
const auto base = address & ~(pageBytes - 1);
const auto found = views.find(base);
if (found == views.end() || !writable(found->second.protection)) return false;
auto& view = found->second;
const auto written = subpagesOf(base, address, address + 1);
invalidate(*view.page, written);
protectSubpages(base, written, view.protection, "resume shared memory write");
view.armed &= static_cast<std::uint8_t>(~written);
return true;
}
bool BeginHostWrite(std::uintptr_t address, std::size_t bytes) {
std::lock_guard lock(mutex);
const auto first = views.lower_bound(address & ~(pageBytes - 1));
const auto end = address + bytes;
for (auto it = first; it != views.end() && it->first < end; ++it) {
if (!writable(it->second.protection)) return false;
}
for (auto it = first; it != views.end() && it->first < end; ++it) {
auto& view = it->second;
++view.hostWrites;
const auto written = subpagesOf(it->first, address, end);
invalidate(*view.page, written);
const auto opened = static_cast<std::uint8_t>(view.armed & written);
if (opened == 0) continue;
protectSubpages(it->first, opened, view.protection, "open shared memory to a host write");
view.armed &= static_cast<std::uint8_t>(~opened);
}
return true;
}
void EndHostWrite(std::uintptr_t address, std::size_t bytes) {
std::lock_guard lock(mutex);
const auto end = address + bytes;
for (auto it = views.lower_bound(address & ~(pageBytes - 1)); it != views.end() && it->first < end; ++it) {
--it->second.hostWrites;
invalidate(*it->second.page, subpagesOf(it->first, address, end));
}
}
void* MapAlias(std::uintptr_t address, std::size_t bytes) {
std::lock_guard lock(mutex);
const auto refuse = [&](const char* reason) {
char text[192];
std::snprintf(text, sizeof(text), "read-write alias of shared guest memory 0x%llx+0x%llx: %s", static_cast<unsigned long long>(address), static_cast<unsigned long long>(bytes), reason);
return std::runtime_error(text);
};
if (address % pageBytes != 0 || bytes % pageBytes != 0 || bytes == 0) throw refuse("the range is not made of whole shared pages");
auto view = views.find(address);
if (view == views.end()) throw refuse("the range does not start at a shared view");
const auto section = view->second.section;
const auto offset = view->second.offset;
SYSTEM_INFO system{};
GetSystemInfo(&system);
bool contiguous = true;
for (std::size_t done = 0; done < bytes; done += pageBytes) {
const auto page = views.find(address + done);
if (page == views.end()) throw refuse("the range is not a run of shared views");
if (page->second.offset != offset + done || (page->second.section != section && !sameSection(page->second.section->handle, section->handle))) contiguous = false;
}
if (!contiguous) return mapAliasRuns(address, bytes);
const auto lead = offset % system.dwAllocationGranularity;
void* alias = map(section->handle, GetCurrentProcess(), nullptr, offset - lead, lead + bytes, 0, PAGE_READWRITE, nullptr, 0);
if (alias == nullptr) {
char text[160];
std::snprintf(text, sizeof(text), "MapViewOfFile3 of a read-write alias of shared guest memory 0x%llx+0x%llx", static_cast<unsigned long long>(address), static_cast<unsigned long long>(bytes));
throw std::system_error(static_cast<int>(GetLastError()), std::system_category(), text);
}
return static_cast<char*>(alias) + lead;
}
void UnmapAlias(void* alias) {
if (alias == nullptr) return;
{
std::lock_guard lock(mutex);
if (const auto found = splitAliases.find(reinterpret_cast<std::uintptr_t>(alias)); found != splitAliases.end()) {
const auto error = releaseAlias(found->first, found->second);
splitAliases.erase(found);
if (error != ERROR_SUCCESS) throw std::system_error(static_cast<int>(error), std::system_category(), "unmap a run of a shared guest alias");
return;
}
}
SYSTEM_INFO system{};
GetSystemInfo(&system);
const auto base = reinterpret_cast<std::uintptr_t>(alias) & ~(static_cast<std::uintptr_t>(system.dwAllocationGranularity) - 1);
if (!unmap(GetCurrentProcess(), reinterpret_cast<void*>(base), 0)) fail("unmap shared guest alias");
}
bool Protection(std::uintptr_t address, std::uint32_t* protection) {
std::lock_guard lock(mutex);
const auto found = views.find(address & ~(pageBytes - 1));
if (found == views.end()) return false;
*protection = found->second.protection;
return true;
}
bool Collect(std::uintptr_t address, std::size_t bytes, void** pages, std::size_t* count, bool clear, Collection collection = Collection::All) {
std::lock_guard lock(mutex);
const auto capacity = *count;
*count = 0;
const auto end = address + bytes;
if (collection == Collection::Fresh) {
const auto fresh = freshRanges.upper_bound(address);
if ((fresh == freshRanges.begin() || std::prev(fresh)->second <= address) && (fresh == freshRanges.end() || fresh->first >= end)) return true;
for (auto it = views.lower_bound(address & ~(pageBytes - 1)); it != views.end() && it->first < end; ++it) {
if (it->second.protection != PAGE_NOACCESS) continue;
for (auto at = std::max(address, it->first); at < std::min(end, it->first + pageBytes); at += 4096) {
if ((it->second.collected & (1u << ((at - it->first) / 4096))) == 0) return false;
}
}
} else if (clear && !collectable(address, end)) return false;
for (auto cursor = address; cursor < end;) {
const auto nextClean = cleanRanges.upper_bound(cursor);
if (nextClean != cleanRanges.begin()) {
const auto clean = std::prev(nextClean);
if (cursor < clean->second) {
cursor = std::min(end, clean->second);
continue;
}
}
const auto base = cursor & ~(pageBytes - 1);
const auto found = views.find(base);
if (found != views.end()) {
auto& view = found->second;
auto stop = std::min(std::min(end, base + pageBytes), nextClean == cleanRanges.end() ? end : nextClean->first);
if (collection != Collection::All) {
const auto selected = [&](std::uintptr_t at) {
const bool collected = (view.collected & (1u << ((at - base) / 4096))) != 0;
return collection == Collection::Armed ? collected : !collected;
};
while (cursor < stop && !selected(cursor)) cursor = std::min(stop, cursor + 4096);
if (cursor == stop) continue;
for (auto at = cursor + 4096; at < stop; at += 4096) {
if (!selected(at)) {
stop = at;
break;
}
}
}
if (view.protection == PAGE_NOACCESS) return false;
const bool pinned = view.page->pins != 0;
std::uint8_t settled = 0;
auto settledEnd = cursor;
bool full = false;
for (std::size_t sub = 0; sub < subpages; ++sub) {
const auto from = std::max(cursor, base + sub * subpageBytes);
const auto to = std::min(stop, base + (sub + 1) * subpageBytes);
if (from >= to) continue;
if (collection == Collection::Fresh || pinned || view.seen[sub] != view.page->generation[sub]) {
if (*count == capacity) {
full = true;
break;
}
pages[(*count)++] = reinterpret_cast<void*>(from);
}
settled |= static_cast<std::uint8_t>(1u << sub);
settledEnd = to;
}
if (clear) {
for (std::size_t sub = 0; sub < subpages; ++sub) {
const auto bit = static_cast<std::uint8_t>(1u << sub);
if ((settled & bit) == 0 || (view.collected & bit) != 0) continue;
view.collected |= bit;
forget(freshRanges, base + sub * subpageBytes, base + (sub + 1) * subpageBytes);
}
}
if (clear && !pinned && settled != 0) {
std::uint8_t untracked = 0;
for (const auto alias : view.page->aliases) {
auto& other = views.at(alias);
const auto arming = static_cast<std::uint8_t>(settled & ~other.armed);
if (!writable(other.protection) || arming == 0) continue;
if (other.hostWrites != 0) {
untracked |= arming;
continue;
}
const DWORD protection = other.protection == PAGE_EXECUTE_READWRITE ? PAGE_EXECUTE_READ : PAGE_READONLY;
protectSubpages(alias, arming, protection, "arm shared memory write tracking");
other.armed |= arming;
}
const auto tracked = static_cast<std::uint8_t>(settled & ~untracked);
auto cleanFrom = cursor;
auto cleanTo = cursor;
for (std::size_t sub = 0; sub < subpages; ++sub) {
if ((tracked & (1u << sub)) == 0) continue;
const auto from = std::max(cursor, base + sub * subpageBytes);
const auto to = std::min(settledEnd, base + (sub + 1) * subpageBytes);
if (from == base + sub * subpageBytes && to == base + (sub + 1) * subpageBytes) view.seen[sub] = view.page->generation[sub];
if (from != cleanTo) {
if (cleanFrom != cleanTo) rememberClean(cleanFrom, cleanTo);
cleanFrom = from;
}
cleanTo = to;
}
if (cleanFrom != cleanTo) rememberClean(cleanFrom, cleanTo);
}
if (full) return true;
cursor = stop;
} else {
if (collection == Collection::Fresh) {
const auto next = views.lower_bound(cursor);
cursor = next == views.end() ? end : std::min(end, next->first);
continue;
}
const auto memory = query(cursor);
if (memory.State != MEM_COMMIT || memory.Type != MEM_PRIVATE) return false;
const auto stop = std::min(end, reinterpret_cast<std::uintptr_t>(memory.BaseAddress) + memory.RegionSize);
ULONG_PTR available = capacity - *count;
if (available == 0) return true;
DWORD granularity = 0;
if (GetWriteWatch(clear ? WRITE_WATCH_FLAG_RESET : 0, reinterpret_cast<void*>(cursor), stop - cursor, pages + *count, &available, &granularity) != 0) fail("collect private guest writes");
*count += available;
if (*count == capacity) return true;
cursor = stop;
}
}
return true;
}
private:
bool collectable(std::uintptr_t address, std::uintptr_t end) {
for (auto cursor = address; cursor < end;) {
const auto base = cursor & ~(pageBytes - 1);
if (const auto found = views.find(base); found != views.end()) {
if (found->second.protection == PAGE_NOACCESS) return false;
cursor = std::min(end, base + pageBytes);
continue;
}
const auto memory = query(cursor);
if (memory.State != MEM_COMMIT || memory.Type != MEM_PRIVATE) return false;
cursor = std::min(end, reinterpret_cast<std::uintptr_t>(memory.BaseAddress) + memory.RegionSize);
}
return true;
}
static constexpr std::size_t pageBytes = 0x4000;
static constexpr std::size_t subpageBytes = 0x1000;
static_assert(subpageBytes == 4096, "Collect reports one page per subpage");
static constexpr std::size_t subpages = pageBytes / subpageBytes;
static constexpr std::uint8_t allSubpages = (1u << subpages) - 1;
struct SharedPage {
std::array<std::uint64_t, subpages> generation = [] {
std::array<std::uint64_t, subpages> initial{};
initial.fill(1);
return initial;
}();
std::vector<std::uintptr_t> aliases;
std::uint32_t pins = 0;
};
struct Section {
HANDLE handle;
explicit Section(HANDLE handle) : handle(handle) {}
Section(const Section&) = delete;
Section& operator=(const Section&) = delete;
~Section() { CloseHandle(handle); }
};
struct View {
std::shared_ptr<SharedPage> page;
DWORD protection;
std::array<std::uint64_t, subpages> seen;
std::uint8_t armed;
std::shared_ptr<Section> section;
std::uint64_t offset;
std::uint32_t hostWrites;
std::uint8_t collected = 0;
};
using Ranges = std::map<std::uintptr_t, std::uintptr_t>;
static void forget(Ranges& ranges, std::uintptr_t start, std::uintptr_t end) {
auto it = ranges.lower_bound(start);
if (it != ranges.begin() && std::prev(it)->second > start) --it;
while (it != ranges.end() && it->first < end) {
const auto first = it->first;
const auto last = it->second;
it = ranges.erase(it);
if (first < start) ranges.emplace(first, start);
if (last > end) it = ranges.emplace(end, last).first;
}
}
static void remember(Ranges& ranges, std::uintptr_t start, std::uintptr_t end) {
auto it = ranges.lower_bound(start);
if (it != ranges.begin() && std::prev(it)->second >= start) --it;
while (it != ranges.end() && it->first <= end) {
start = std::min(start, it->first);
end = std::max(end, it->second);
it = ranges.erase(it);
}
ranges.emplace(start, end);
}
void forgetClean(std::uintptr_t start, std::uintptr_t end) {
forget(cleanRanges, start, end);
}
void rememberClean(std::uintptr_t start, std::uintptr_t end) {
remember(cleanRanges, start, end);
}
void invalidate(SharedPage& page, std::uint8_t mask = allSubpages) {
for (std::size_t sub = 0; sub < subpages; ++sub) {
if ((mask & (1u << sub)) == 0) continue;
++page.generation[sub];
for (const auto alias : page.aliases) forgetClean(alias + sub * subpageBytes, alias + (sub + 1) * subpageBytes);
}
}
static std::uint8_t subpagesOf(std::uintptr_t base, std::uintptr_t address, std::uintptr_t end) {
const auto from = std::max(address, base);
const auto to = std::min(end, base + pageBytes);
if (to <= from) return 0;
const auto first = (from - base) / subpageBytes;
const auto last = (to - 1 - base) / subpageBytes;
return static_cast<std::uint8_t>(((1u << (last + 1)) - 1) & ~((1u << first) - 1));
}
static void protectSubpages(std::uintptr_t base, std::uint8_t mask, DWORD protection, const char* operation) {
for (std::size_t sub = 0; sub < subpages;) {
if ((mask & (1u << sub)) == 0) {
++sub;
continue;
}
auto stop = sub;
while (stop < subpages && (mask & (1u << stop)) != 0) ++stop;
DWORD previous;
if (!VirtualProtect(reinterpret_cast<void*>(base + sub * subpageBytes), (stop - sub) * subpageBytes, protection, &previous)) fail(operation);
sub = stop;
}
}
bool sameSection(HANDLE first, HANDLE second) const {
return compare != nullptr && compare(first, second);
}
static bool writable(DWORD protection) {
return protection == PAGE_READWRITE || protection == PAGE_EXECUTE_READWRITE;
}
using AllocateFunction = PVOID (WINAPI*)(HANDLE, PVOID, SIZE_T, ULONG, ULONG, MEM_EXTENDED_PARAMETER*, ULONG);
using MapFunction = PVOID (WINAPI*)(HANDLE, HANDLE, PVOID, ULONG64, SIZE_T, ULONG, ULONG, MEM_EXTENDED_PARAMETER*, ULONG);
using UnmapFunction = BOOL (WINAPI*)(HANDLE, PVOID, ULONG);
using CompareFunction = BOOL (WINAPI*)(HANDLE, HANDLE);
WindowsMappings() {
const auto module = GetModuleHandleW(L"KernelBase.dll");
if (!module) fail("load Windows memory API");
allocate = reinterpret_cast<AllocateFunction>(GetProcAddress(module, "VirtualAlloc2"));
map = reinterpret_cast<MapFunction>(GetProcAddress(module, "MapViewOfFile3"));
unmap = reinterpret_cast<UnmapFunction>(GetProcAddress(module, "UnmapViewOfFile2"));
if (!allocate || !map || !unmap) throw std::runtime_error("Windows placeholder memory APIs are required");
compare = reinterpret_cast<CompareFunction>(GetProcAddress(module, "CompareObjectHandles"));
}
[[noreturn]] static void fail(const char* operation) {
throw std::system_error(static_cast<int>(GetLastError()), std::system_category(), operation);
}
static MEMORY_BASIC_INFORMATION query(std::uintptr_t address) {
MEMORY_BASIC_INFORMATION memory{};
if (VirtualQuery(reinterpret_cast<void*>(address), &memory, sizeof(memory)) != sizeof(memory)) fail("query guest memory");
return memory;
}
static void split(std::uintptr_t address, std::size_t bytes) {
auto memory = query(address);
memory = query(reinterpret_cast<std::uintptr_t>(memory.AllocationBase));
if (memory.State != MEM_RESERVE) throw std::runtime_error("guest mapping requires a placeholder");
const auto base = reinterpret_cast<std::uintptr_t>(memory.BaseAddress);
if (address != base) {
if (!VirtualFree(reinterpret_cast<void*>(base), address - base, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) fail("split guest placeholder prefix");
memory = query(address);
}
if (memory.RegionSize < bytes) throw std::runtime_error("guest placeholder is too small");
if (memory.RegionSize != bytes && !VirtualFree(reinterpret_cast<void*>(address), bytes, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) fail("split guest placeholder suffix");
}
void reset(std::uintptr_t address, std::size_t bytes) {
const auto end = address + bytes;
forgetClean(address, end);
for (auto cursor = address; cursor < end;) {
const auto memory = query(cursor);
if (memory.State == MEM_RESERVE) {
cursor = std::min(end, reinterpret_cast<std::uintptr_t>(memory.BaseAddress) + memory.RegionSize);
continue;
}
if (reinterpret_cast<std::uintptr_t>(memory.AllocationBase) != cursor) throw std::runtime_error("cannot release part of a host allocation");
auto allocationEnd = cursor;
do {
const auto part = query(allocationEnd);
if (part.AllocationBase != memory.AllocationBase) break;
allocationEnd = reinterpret_cast<std::uintptr_t>(part.BaseAddress) + part.RegionSize;
} while (allocationEnd < end);
if (allocationEnd > end || query(allocationEnd).AllocationBase == memory.AllocationBase) throw std::runtime_error("guest release truncates a host allocation");
if (memory.Type == MEM_MAPPED) {
if (!unmap(GetCurrentProcess(), reinterpret_cast<void*>(cursor), MEM_PRESERVE_PLACEHOLDER)) fail("unmap shared guest page");
const auto found = views.find(cursor);
if (found != views.end()) {
std::erase(found->second.page->aliases, cursor);
views.erase(found);
forget(freshRanges, cursor, cursor + pageBytes);
}
} else if (memory.Type == MEM_PRIVATE) {
if (!VirtualFree(reinterpret_cast<void*>(cursor), allocationEnd - cursor, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) fail("release private guest memory");
} else {
throw std::runtime_error("unsupported guest mapping type");
}
cursor = allocationEnd;
}
const auto last = query(reinterpret_cast<std::uintptr_t>(query(end - 1).AllocationBase));
const auto lastBase = reinterpret_cast<std::uintptr_t>(last.BaseAddress);
if (lastBase + last.RegionSize > end) split(lastBase, end - lastBase);
const auto first = query(address);
split(address, std::min(bytes, reinterpret_cast<std::uintptr_t>(first.BaseAddress) + first.RegionSize - address));
if (query(address).RegionSize != bytes && !VirtualFree(reinterpret_cast<void*>(address), bytes, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS)) fail("coalesce guest placeholders");
}
void* mapAliasRuns(std::uintptr_t address, std::size_t bytes) {
auto* reserved = allocate(GetCurrentProcess(), nullptr, bytes, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, nullptr, 0);
if (reserved == nullptr) fail("reserve a read-write alias of shared guest memory");
const auto base = reinterpret_cast<std::uintptr_t>(reserved);
try {
for (std::size_t done = 0; done < bytes;) {
const auto& first = views.at(address + done);
auto run = pageBytes;
while (done + run < bytes) {
const auto& next = views.at(address + done + run);
if (next.offset != first.offset + run || (next.section != first.section && !sameSection(next.section->handle, first.section->handle))) break;
run += pageBytes;
}
split(base + done, run);
if (!map(first.section->handle, GetCurrentProcess(), reinterpret_cast<void*>(base + done), first.offset, run, MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, nullptr, 0)) fail("map a run of a read-write alias of shared guest memory");
done += run;
}
} catch (...) {
releaseAlias(base, bytes);
throw;
}
splitAliases.emplace(base, bytes);
return reserved;
}
DWORD releaseAlias(std::uintptr_t base, std::size_t bytes) {
DWORD error = ERROR_SUCCESS;
for (auto cursor = base; cursor < base + bytes;) {
const auto memory = query(cursor);
const auto released = memory.State == MEM_RESERVE ? VirtualFree(memory.BaseAddress, 0, MEM_RELEASE) : unmap(GetCurrentProcess(), memory.BaseAddress, 0);
if (!released && error == ERROR_SUCCESS) error = GetLastError();
cursor = reinterpret_cast<std::uintptr_t>(memory.BaseAddress) + memory.RegionSize;
}
return error;
}
Ranges cleanRanges;
Ranges freshRanges;
std::map<std::uintptr_t, View> views;
std::map<std::pair<std::uintptr_t, std::uint64_t>, std::weak_ptr<SharedPage>> physical;
std::map<std::uintptr_t, std::size_t> splitAliases;
std::mutex mutex;
AllocateFunction allocate = nullptr;
MapFunction map = nullptr;
UnmapFunction unmap = nullptr;
CompareFunction compare = nullptr;
};
}
#endif
#endif