mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-28 12:05:06 +00:00
A private guest commit on Windows (every anonymous kernel map commits with a 16 KiB granule) ran reset() on each page before replacing it: 10 VirtualQuery calls, a placeholder split and the VirtualAlloc2, so 12 calls per page. At the arena's first-fit frontier the placeholder is the arena's free tail, hundreds of GiB. Commit now queries a placeholder once, cuts the range out of it (one split where the range starts inside the placeholder, one where the placeholder extends past the range), and carves the pages from that bounded placeholder: one split and one VirtualAlloc2 per page, and no split for the last page. Each page is still its own allocation with the same protection, write watch and created ranges, so munmap, MAP_FIXED remaps, views and collects see the same layout as before. A piece that would cross the end of its placeholder (a whole-range granule over several placeholders) still takes the coalescing path through reset(), and committed memory in the range still only changes its protection. A zero granule now throws std::invalid_argument before touching the address space. windows_write_watch counts the queries with a compile-time counter that only that test defines (APS5_COUNT_MAPPING_QUERIES, copied from #9505) and checks: 256 fresh pages one page into a 1 GiB placeholder take one query (main's loop makes 2,561), each page is its own allocation and the placeholders around them are intact; the release and recommit of one page; a protection-only commit; the write watch of carved pages; a whole-range granule inside one placeholder and across two; committed pieces and adjacent placeholders inside the range; a partial release and a fixed remap that keep their neighbours; a granule that would cross the end of the placeholder being carved, which takes the coalescing path; a short last piece; a commit that runs to the end of its placeholder; an unwatched commit; and the zero-granule throw. PPSA14363 boot on an RX 6700 XT, idle PC, 4 runs on main and 3 with this change: the first 10 s after the first present go from 546-552 presents with a 578-704 ms frame to 588-594 presents with a 97-117 ms frame. Co-Authored-By: Claude <[email protected]>
386 lines
23 KiB
C++
386 lines
23 KiB
C++
#define NOMINMAX
|
|
#include "prx/libc/include/WindowsMappings.hpp"
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <iostream>
|
|
#include <set>
|
|
#include <thread>
|
|
|
|
namespace {
|
|
using Mappings = GuestArena::WindowsMappings;
|
|
using Collection = Mappings::Collection;
|
|
constexpr std::size_t Small = 4096;
|
|
constexpr std::size_t Bytes = 65536;
|
|
std::atomic<unsigned> faults{0};
|
|
|
|
void Require(bool condition, const char* message) {
|
|
if (!condition) throw std::runtime_error(message);
|
|
}
|
|
|
|
LONG CALLBACK HandleWrite(EXCEPTION_POINTERS* exception) {
|
|
const auto* record = exception->ExceptionRecord;
|
|
if (record->ExceptionCode != EXCEPTION_ACCESS_VIOLATION || record->NumberParameters < 2 || record->ExceptionInformation[0] != 1) return EXCEPTION_CONTINUE_SEARCH;
|
|
if (!Mappings::Get().HandleWrite(record->ExceptionInformation[1])) return EXCEPTION_CONTINUE_SEARCH;
|
|
++faults;
|
|
return EXCEPTION_CONTINUE_EXECUTION;
|
|
}
|
|
|
|
struct Region {
|
|
HANDLE section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE | SEC_RESERVE, 0, Bytes, nullptr);
|
|
unsigned char* first = static_cast<unsigned char*>(Mappings::Get().Reserve(nullptr, Bytes));
|
|
unsigned char* alias = static_cast<unsigned char*>(Mappings::Get().Reserve(nullptr, Bytes));
|
|
|
|
Region() {
|
|
Require(section && first && alias, "cannot allocate shared test memory");
|
|
Mappings::Get().Map(first, Bytes, section, 0, PAGE_READWRITE);
|
|
Mappings::Get().Map(alias, Bytes, section, 0, PAGE_READWRITE);
|
|
}
|
|
~Region() {
|
|
Mappings::Get().Reset(first, Bytes);
|
|
Mappings::Get().Reset(alias, Bytes);
|
|
VirtualFree(first, 0, MEM_RELEASE);
|
|
VirtualFree(alias, 0, MEM_RELEASE);
|
|
CloseHandle(section);
|
|
}
|
|
void Write(std::size_t page, unsigned char value) {
|
|
static_cast<volatile unsigned char*>(first)[page * Small] = value;
|
|
}
|
|
std::set<std::size_t> Collect(Collection mode, std::size_t begin = 0, std::size_t count = 16, bool clear = true, bool useAlias = false, std::size_t capacity = 32) {
|
|
auto* base = useAlias ? alias : first;
|
|
const auto address = reinterpret_cast<std::uintptr_t>(base);
|
|
auto cursor = address + begin * Small;
|
|
const auto end = cursor + count * Small;
|
|
std::set<std::size_t> result;
|
|
while (cursor < end) {
|
|
std::array<void*, 32> pages{};
|
|
auto available = capacity;
|
|
Require(Mappings::Get().Collect(cursor, end - cursor, pages.data(), &available, clear, mode), "shared collection failed");
|
|
for (std::size_t i = 0; i < available; ++i) result.insert((reinterpret_cast<std::uintptr_t>(pages[i]) - address) / Small);
|
|
if (available < capacity) break;
|
|
cursor = reinterpret_cast<std::uintptr_t>(pages[available - 1]) + Small;
|
|
}
|
|
return result;
|
|
}
|
|
DWORD Protection(std::size_t page, bool useAlias = false) {
|
|
MEMORY_BASIC_INFORMATION info{};
|
|
Require(VirtualQuery((useAlias ? alias : first) + page * Small, &info, sizeof(info)) == sizeof(info), "protection query failed");
|
|
return info.Protect;
|
|
}
|
|
};
|
|
|
|
std::set<std::size_t> Pages(std::size_t begin, std::size_t end) {
|
|
std::set<std::size_t> result;
|
|
for (auto page = begin; page < end; ++page) result.insert(page);
|
|
return result;
|
|
}
|
|
|
|
void CheckArmed() {
|
|
Region region;
|
|
Require(region.Collect(Collection::Armed).empty(), "import collected fresh memory");
|
|
const auto before = faults.load();
|
|
region.Write(0, 1);
|
|
region.Write(4, 2);
|
|
Require(faults == before, "import armed fresh memory");
|
|
Require(region.Collect(Collection::All, 0, 1) == Pages(0, 1), "first partial collect failed");
|
|
Require(region.Protection(0) == PAGE_READONLY && region.Protection(3) == PAGE_READWRITE, "partial collect did not protect only its page");
|
|
region.Write(0, 3);
|
|
region.Write(4, 4);
|
|
Require(region.Collect(Collection::Armed) == Pages(0, 1), "armed collect lost a write or consumed fresh pages");
|
|
Require(region.Protection(4) == PAGE_READWRITE, "armed collect protected another fresh view");
|
|
Require(region.Collect(Collection::All) == Pages(1, 16), "ordinary collect lost fresh pages");
|
|
Require(region.Collect(Collection::All).empty(), "cleared memory reports writes");
|
|
}
|
|
|
|
void CheckFresh() {
|
|
Region region;
|
|
Require(region.Collect(Collection::All, 0, 1) == Pages(0, 1), "cannot collect initial page");
|
|
region.Write(0, 7);
|
|
Require(region.Collect(Collection::Fresh) == Pages(1, 16), "fresh collect consumed an existing dirty page");
|
|
Require(region.Collect(Collection::Fresh).empty(), "fresh collect repeated initial writes");
|
|
Require(region.Collect(Collection::Armed) == Pages(0, 1), "fresh collect cleared an existing write");
|
|
region.Write(9, 9);
|
|
Require(region.Collect(Collection::Armed) == Pages(9, 10), "write after activation was missed");
|
|
}
|
|
|
|
void CheckAliasesAndCapacity() {
|
|
Region region;
|
|
Require(region.Collect(Collection::All, 0, 1) == Pages(0, 1), "initial collect failed");
|
|
Require(region.Protection(0, true) == PAGE_READONLY, "alias was not protected");
|
|
Require(region.Collect(Collection::Armed, 0, 16, true, true).empty(), "alias protection was mistaken for collection history");
|
|
Require(region.Collect(Collection::Fresh, 0, 16, true, true, 1) == Pages(0, 16), "small output buffers lost fresh pages");
|
|
Require(region.Collect(Collection::Fresh, 0, 16, true, true).empty(), "small output buffers did not finish fresh collection");
|
|
static_cast<volatile unsigned char*>(region.alias)[0] = 8;
|
|
Require(region.Collect(Collection::Armed) == Pages(0, 1), "alias write was missed or fresh neighbors were consumed");
|
|
Require(region.Collect(Collection::Fresh) == Pages(1, 16), "alias collection changed another view's history");
|
|
Mappings::Get().Reset(region.first, Bytes);
|
|
Mappings::Get().Map(region.first, Bytes, region.section, 0, PAGE_READWRITE);
|
|
Require(region.Collect(Collection::Armed).empty(), "remapped address kept old collection history");
|
|
Require(region.Collect(Collection::Fresh) == Pages(0, 16), "remapped memory was not fresh");
|
|
}
|
|
|
|
void CheckPinsAndHostWrites() {
|
|
Region region;
|
|
const auto address = reinterpret_cast<std::uintptr_t>(region.first);
|
|
Mappings::Get().Pin(address, 16384);
|
|
Require(region.Collect(Collection::Fresh) == Pages(0, 16), "pinned fresh collection failed");
|
|
Require(region.Protection(0) == PAGE_READWRITE, "pinned page was protected");
|
|
Require(region.Collect(Collection::Armed) == Pages(0, 4), "pinned writes were trusted as clean");
|
|
Mappings::Get().Unpin(address, 16384);
|
|
Require(region.Collect(Collection::Armed) == Pages(0, 4), "unpin was not invalidated");
|
|
Require(region.Collect(Collection::Armed).empty(), "unpin did not restore tracking");
|
|
Require(Mappings::Get().BeginHostWrite(address, 16384), "host write refused");
|
|
region.Write(0, 3);
|
|
region.Collect(Collection::Armed);
|
|
region.Write(0, 4);
|
|
Mappings::Get().EndHostWrite(address, 16384);
|
|
Require(region.Collect(Collection::Armed) == Pages(0, 4), "host write completion was missed");
|
|
Require(region.Collect(Collection::Armed).empty(), "completed host write stayed dirty");
|
|
}
|
|
|
|
void CheckFailureAndHoles() {
|
|
Region region;
|
|
const auto address = reinterpret_cast<std::uintptr_t>(region.first);
|
|
DWORD previous;
|
|
Mappings::Get().SetProtection(address + 16384, 16384, PAGE_NOACCESS);
|
|
Require(VirtualProtect(region.first + 16384, 16384, PAGE_NOACCESS, &previous), "cannot protect test page");
|
|
std::array<void*, 32> pages{};
|
|
auto count = pages.size();
|
|
Require(!Mappings::Get().Collect(address, Bytes, pages.data(), &count, true, Collection::Fresh), "fresh inaccessible page accepted");
|
|
Require(region.Protection(0) == PAGE_READWRITE, "failed collect changed an earlier page");
|
|
Mappings::Get().SetProtection(address + 16384, 16384, PAGE_READWRITE);
|
|
Require(VirtualProtect(region.first + 16384, 16384, PAGE_READWRITE, &previous), "cannot restore test page");
|
|
Mappings::Get().Reset(region.first + 16384, 16384);
|
|
Require(region.Collect(Collection::Fresh) == std::set<std::size_t>({0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15}), "fresh collection did not tolerate an unmapped block neighbor");
|
|
Require(region.Collect(Collection::Fresh).empty(), "holes caused fresh pages to repeat");
|
|
}
|
|
|
|
void CheckPrivate() {
|
|
auto& mappings = Mappings::Get();
|
|
auto* memory = static_cast<unsigned char*>(mappings.Reserve(nullptr, Bytes));
|
|
Require(memory != nullptr, "private reservation failed");
|
|
mappings.Commit(memory, Bytes, PAGE_READWRITE, Bytes, true);
|
|
const auto address = reinterpret_cast<std::uintptr_t>(memory);
|
|
std::array<void*, 32> pages{};
|
|
std::size_t count = pages.size();
|
|
mappings.Collect(address, Bytes, pages.data(), &count, true);
|
|
static_cast<volatile unsigned char*>(memory)[Small] = 3;
|
|
count = pages.size();
|
|
Require(mappings.Collect(address, Bytes, pages.data(), &count, true, Collection::Fresh) && count == 0, "fresh collect touched private memory");
|
|
count = pages.size();
|
|
Require(mappings.Collect(address, Bytes, pages.data(), &count, true, Collection::Armed) && count == 1 && pages[0] == memory + Small, "private write watch changed");
|
|
mappings.Reset(memory, Bytes);
|
|
VirtualFree(memory, 0, MEM_RELEASE);
|
|
}
|
|
|
|
MEMORY_BASIC_INFORMATION RegionAt(const void* address) {
|
|
MEMORY_BASIC_INFORMATION info{};
|
|
Require(VirtualQuery(address, &info, sizeof(info)) == sizeof(info), "private commit query failed");
|
|
return info;
|
|
}
|
|
|
|
void CheckPrivateCommitCarvesPlaceholders() {
|
|
auto& mappings = Mappings::Get();
|
|
constexpr std::size_t Page = 16384;
|
|
constexpr std::size_t Pages = 256;
|
|
constexpr std::size_t Span = Pages * Page;
|
|
constexpr std::size_t Reserved = std::size_t{1} << 30;
|
|
auto* holder = static_cast<unsigned char*>(mappings.Reserve(nullptr, Reserved));
|
|
Require(holder != nullptr, "cannot reserve a placeholder for private commits");
|
|
auto* memory = holder + Page;
|
|
const auto page = [&](std::size_t index) { return memory + index * Page; };
|
|
const auto at = [](const unsigned char* pointer) { return reinterpret_cast<std::uintptr_t>(pointer); };
|
|
auto before = Mappings::MemoryQueries();
|
|
auto created = mappings.Commit(memory, Span, PAGE_READWRITE, Page, true);
|
|
const auto queries = Mappings::MemoryQueries() - before;
|
|
Require(queries == 1, ("committing 256 fresh pages queried the address space " + std::to_string(queries) + " times").c_str());
|
|
Require(created.size() == 1 && created[0].first == at(memory) && created[0].second == Span, "a fresh commit did not report its range as one created range");
|
|
for (std::size_t index = 0; index < Pages; ++index) {
|
|
const auto info = RegionAt(page(index));
|
|
Require(info.State == MEM_COMMIT && info.Type == MEM_PRIVATE && info.AllocationBase == page(index) && info.RegionSize == Page && info.Protect == PAGE_READWRITE, "a committed page is not a read-write allocation of its own");
|
|
}
|
|
const auto prefix = RegionAt(holder);
|
|
Require(prefix.State == MEM_RESERVE && prefix.AllocationBase == holder && prefix.RegionSize == Page, "the placeholder before the commit changed");
|
|
const auto suffix = RegionAt(page(Pages));
|
|
Require(suffix.State == MEM_RESERVE && suffix.AllocationBase == page(Pages) && suffix.RegionSize == Reserved - Page - Span, "the placeholder after the commit changed");
|
|
for (std::size_t index = 0; index < Pages; ++index) page(index)[8] = static_cast<unsigned char>(index + 1);
|
|
mappings.Reset(page(11), Page);
|
|
const auto released = RegionAt(page(11));
|
|
Require(released.State == MEM_RESERVE && released.AllocationBase == page(11) && released.RegionSize == Page, "a released page did not become a placeholder of its own");
|
|
before = Mappings::MemoryQueries();
|
|
created = mappings.Commit(page(11), Page, PAGE_READWRITE, Page, true);
|
|
Require(Mappings::MemoryQueries() - before <= 1 && created.size() == 1 && created[0].first == at(page(11)) && created[0].second == Page && page(11)[8] == 0, "recommitting a released page was not one fresh allocation");
|
|
for (std::size_t index = 0; index < Pages; ++index) {
|
|
if (index != 11) Require(page(index)[8] == static_cast<unsigned char>(index + 1), "a release or commit changed a neighbouring page");
|
|
}
|
|
created = mappings.Commit(memory, Span, PAGE_READONLY, Page, true);
|
|
Require(created.empty() && RegionAt(page(0)).Protect == PAGE_READONLY && RegionAt(page(Pages - 1)).Protect == PAGE_READONLY, "a protection change reported fresh memory or missed a page");
|
|
mappings.Commit(memory, Span, PAGE_READWRITE, Page, true);
|
|
std::array<void*, 32> written{};
|
|
std::size_t count = written.size();
|
|
mappings.Collect(at(page(40)), 4 * Page, written.data(), &count, true);
|
|
page(41)[4096 + 3] = 1;
|
|
count = written.size();
|
|
Require(mappings.Collect(at(page(40)), 4 * Page, written.data(), &count, true, Collection::Armed) && count == 1 && written[0] == page(41) + 4096, "a carved page lost its write watch");
|
|
mappings.Reset(page(20), 2 * Page);
|
|
mappings.Reset(page(22), 2 * Page);
|
|
created = mappings.Commit(page(20), 4 * Page, PAGE_READWRITE, 4 * Page, true);
|
|
Require(created.size() == 1 && created[0].second == 4 * Page && RegionAt(page(23)).AllocationBase == page(20), "a commit over two placeholders was not one allocation");
|
|
mappings.Reset(memory, Span);
|
|
Require(VirtualFree(holder, Reserved, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS) && VirtualFree(holder, 0, MEM_RELEASE), "cannot release the private commit placeholder");
|
|
}
|
|
|
|
void CheckPrivateCommitEdgeLayouts() {
|
|
auto& mappings = Mappings::Get();
|
|
constexpr std::size_t Page = 16384;
|
|
constexpr std::size_t Pages = 32;
|
|
auto* memory = static_cast<unsigned char*>(mappings.Reserve(nullptr, Pages * Page));
|
|
Require(memory != nullptr, "cannot reserve a placeholder for private commit edges");
|
|
const auto page = [&](std::size_t index) { return memory + index * Page; };
|
|
const auto range = [&](std::size_t first, std::size_t count) { return std::make_pair(reinterpret_cast<std::uintptr_t>(page(first)), count * Page); };
|
|
const auto region = [&](std::size_t first, std::size_t count, DWORD state) {
|
|
const auto info = RegionAt(page(first));
|
|
return info.State == state && info.AllocationBase == page(first) && info.RegionSize == count * Page;
|
|
};
|
|
const auto ownPages = [&](std::size_t first, std::size_t count) {
|
|
for (auto index = first; index < first + count; ++index) {
|
|
if (!region(index, 1, MEM_COMMIT)) return false;
|
|
}
|
|
return true;
|
|
};
|
|
auto before = Mappings::MemoryQueries();
|
|
auto created = mappings.Commit(page(2), 4 * Page, PAGE_READWRITE, 4 * Page, true);
|
|
Require(Mappings::MemoryQueries() - before <= 1 && created.size() == 1 && created[0] == range(2, 4), "a whole-range granule inside a placeholder was not one created range from one query");
|
|
Require(region(0, 2, MEM_RESERVE) && region(2, 4, MEM_COMMIT) && region(6, Pages - 6, MEM_RESERVE), "a whole-range granule inside a placeholder was not one allocation between two placeholders");
|
|
page(2)[8] = 0x5a;
|
|
before = Mappings::MemoryQueries();
|
|
created = mappings.Commit(page(0), 8 * Page, PAGE_READWRITE, Page, true);
|
|
Require(Mappings::MemoryQueries() - before <= 3 && created.size() == 2 && created[0] == range(0, 2) && created[1] == range(6, 2), "a commit around a committed piece did not create exactly the placeholders around it");
|
|
Require(ownPages(0, 2) && region(2, 4, MEM_COMMIT) && page(2)[8] == 0x5a && ownPages(6, 2) && region(8, Pages - 8, MEM_RESERVE), "a commit around a committed piece changed the piece or the layout");
|
|
for (std::size_t index = 0; index < 8; ++index) page(index)[8] = static_cast<unsigned char>(index + 1);
|
|
mappings.Reset(page(6), 2 * Page);
|
|
Require(region(6, 2, MEM_RESERVE) && ownPages(0, 2) && region(2, 4, MEM_COMMIT) && page(1)[8] == 2 && page(5)[8] == 6 && region(8, Pages - 8, MEM_RESERVE), "a partial release of carved pages changed a neighbour");
|
|
mappings.Reset(page(2), 4 * Page);
|
|
before = Mappings::MemoryQueries();
|
|
created = mappings.Commit(page(2), 4 * Page, PAGE_READWRITE, Page, true);
|
|
Require(Mappings::MemoryQueries() - before <= 1 && created.size() == 1 && created[0] == range(2, 4) && ownPages(2, 4) && page(2)[8] == 0 && page(5)[8] == 0, "a fixed remap was not carved into fresh pages");
|
|
Require(page(1)[8] == 2 && region(6, 2, MEM_RESERVE), "a fixed remap changed a neighbour");
|
|
before = Mappings::MemoryQueries();
|
|
created = mappings.Commit(page(6), 4 * Page, PAGE_READWRITE, Page, true);
|
|
Require(Mappings::MemoryQueries() - before <= 2 && created.size() == 1 && created[0] == range(6, 4) && ownPages(6, 4) && region(10, Pages - 10, MEM_RESERVE), "a commit over adjacent placeholders was not carved page by page");
|
|
bool refused = false;
|
|
try {
|
|
mappings.Commit(page(12), Page, PAGE_READWRITE, 0, true);
|
|
} catch (const std::invalid_argument&) {
|
|
refused = true;
|
|
}
|
|
Require(refused && region(10, Pages - 10, MEM_RESERVE), "a zero commit granule was accepted or changed the placeholder");
|
|
mappings.Commit(page(10), 3 * Page, PAGE_READWRITE, 3 * Page, true);
|
|
mappings.Reset(page(10), 3 * Page);
|
|
created = mappings.Commit(page(10), 4 * Page, PAGE_READWRITE, 2 * Page, true);
|
|
Require(created.size() == 1 && created[0] == range(10, 4) && region(10, 2, MEM_COMMIT) && region(12, 2, MEM_COMMIT) && region(14, Pages - 14, MEM_RESERVE), "a granule crossing the end of a carved placeholder did not take the coalescing path");
|
|
mappings.Reset(memory, 14 * Page);
|
|
Require(VirtualFree(memory, Pages * Page, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS) && VirtualFree(memory, 0, MEM_RELEASE), "cannot release the private commit edge placeholder");
|
|
}
|
|
|
|
void CheckPrivateCommitTails() {
|
|
auto& mappings = Mappings::Get();
|
|
constexpr std::size_t Page = 16384;
|
|
constexpr std::size_t Pages = 8;
|
|
auto* memory = static_cast<unsigned char*>(mappings.Reserve(nullptr, Pages * Page));
|
|
Require(memory != nullptr, "cannot reserve a placeholder for private commit tails");
|
|
const auto page = [&](std::size_t index) { return memory + index * Page; };
|
|
const auto at = [](const unsigned char* pointer) { return reinterpret_cast<std::uintptr_t>(pointer); };
|
|
const auto allocation = [](const unsigned char* base, std::size_t bytes, DWORD state) {
|
|
const auto info = RegionAt(base);
|
|
return info.State == state && info.AllocationBase == base && info.RegionSize == bytes;
|
|
};
|
|
const auto unwatched = [](unsigned char* base) {
|
|
std::array<void*, 4> written{};
|
|
ULONG_PTR count = written.size();
|
|
DWORD granularity = 0;
|
|
return GetWriteWatch(0, base, Small, written.data(), &count, &granularity) != 0;
|
|
};
|
|
auto before = Mappings::MemoryQueries();
|
|
auto created = mappings.Commit(page(1), 2 * Page + Small, PAGE_READWRITE, Page, false);
|
|
Require(Mappings::MemoryQueries() - before == 1 && created.size() == 1 && created[0].first == at(page(1)) && created[0].second == 2 * Page + Small, "a commit with a short last piece was not one created range from one query");
|
|
Require(allocation(page(0), Page, MEM_RESERVE) && allocation(page(1), Page, MEM_COMMIT) && allocation(page(2), Page, MEM_COMMIT) && allocation(page(3), Small, MEM_COMMIT) && allocation(page(3) + Small, (Pages - 3) * Page - Small, MEM_RESERVE), "a short last piece was not its own allocation before the rest of the placeholder");
|
|
page(3)[Small - 1] = 7;
|
|
Require(unwatched(page(1)) && unwatched(page(3)), "an unwatched commit carved write-watched pages");
|
|
before = Mappings::MemoryQueries();
|
|
created = mappings.Commit(page(5), 3 * Page, PAGE_READWRITE, Page, true);
|
|
Require(Mappings::MemoryQueries() - before == 1 && created.size() == 1 && created[0].first == at(page(5)) && created[0].second == 3 * Page, "a commit to the end of a placeholder was not one created range from one query");
|
|
Require(allocation(page(3) + Small, 2 * Page - Small, MEM_RESERVE) && allocation(page(5), Page, MEM_COMMIT) && allocation(page(6), Page, MEM_COMMIT) && allocation(page(7), Page, MEM_COMMIT) && page(3)[Small - 1] == 7, "a commit to the end of a placeholder changed the placeholder before it or a neighbour");
|
|
mappings.Reset(page(1), 2 * Page + Small);
|
|
mappings.Reset(page(5), 3 * Page);
|
|
Require(VirtualFree(memory, Pages * Page, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS) && VirtualFree(memory, 0, MEM_RELEASE), "cannot release the private commit tail placeholder");
|
|
}
|
|
|
|
void CheckForgottenSectionKeepsNoPages() {
|
|
auto& mappings = Mappings::Get();
|
|
HANDLE section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE | SEC_RESERVE, 0, Bytes, nullptr);
|
|
auto* first = static_cast<unsigned char*>(mappings.Reserve(nullptr, Bytes));
|
|
auto* second = static_cast<unsigned char*>(mappings.Reserve(nullptr, Bytes));
|
|
Require(section && first && second, "cannot allocate forgotten section test memory");
|
|
const auto resident = [&](unsigned char* address) {
|
|
MEMORY_BASIC_INFORMATION info{};
|
|
Require(VirtualQuery(address, &info, sizeof(info)) == sizeof(info), "resident query failed");
|
|
return info.State == MEM_COMMIT && info.Type == MEM_PRIVATE;
|
|
};
|
|
mappings.Map(first, Bytes, section, 0, PAGE_READWRITE, true, true);
|
|
mappings.Map(second, Bytes, section, 0, PAGE_READWRITE, true, true);
|
|
Require(!resident(first) && !resident(second), "a second mapping kept the first resident");
|
|
mappings.ForgetSection(section);
|
|
mappings.Reset(first, Bytes);
|
|
mappings.Reset(second, Bytes);
|
|
mappings.Map(first, Bytes, section, 0, PAGE_READWRITE, true, true);
|
|
Require(resident(first), "a forgotten section's handle value kept its pages in the section");
|
|
first[0] = 7;
|
|
mappings.ForgetSection(section);
|
|
mappings.Reset(first, Bytes);
|
|
mappings.Map(first, Bytes, section, 0, PAGE_READWRITE, true, true);
|
|
Require(resident(first) && first[0] == 0, "a forgotten section's handle value kept its unmapped bytes");
|
|
mappings.Reset(first, Bytes);
|
|
VirtualFree(first, 0, MEM_RELEASE);
|
|
VirtualFree(second, 0, MEM_RELEASE);
|
|
CloseHandle(section);
|
|
}
|
|
|
|
void CheckConcurrentWrites() {
|
|
Region region;
|
|
std::atomic<bool> start{false};
|
|
std::thread writer([&] {
|
|
while (!start.load(std::memory_order_acquire)) std::this_thread::yield();
|
|
for (std::size_t page = 0; page < 16; ++page) region.Write(page, static_cast<unsigned char>(page + 1));
|
|
});
|
|
start.store(true, std::memory_order_release);
|
|
region.Collect(Collection::Fresh);
|
|
writer.join();
|
|
for (std::size_t page = 0; page < 16; ++page) Require(region.first[page * Small] == page + 1, "activation lost a concurrent write");
|
|
region.Collect(Collection::Armed);
|
|
Require(region.Collect(Collection::Armed).empty(), "concurrent writes did not settle");
|
|
}
|
|
}
|
|
|
|
int main() {
|
|
auto* handler = AddVectoredExceptionHandler(1, HandleWrite);
|
|
try {
|
|
Require(handler != nullptr, "cannot install test handler");
|
|
CheckArmed();
|
|
CheckFresh();
|
|
CheckAliasesAndCapacity();
|
|
CheckPinsAndHostWrites();
|
|
CheckFailureAndHoles();
|
|
CheckPrivate();
|
|
CheckConcurrentWrites();
|
|
CheckForgottenSectionKeepsNoPages();
|
|
CheckPrivateCommitCarvesPlaceholders();
|
|
CheckPrivateCommitEdgeLayouts();
|
|
CheckPrivateCommitTails();
|
|
std::cout << "Windows deferred write-watch tests passed\n";
|
|
} catch (const std::exception& error) {
|
|
std::cerr << error.what() << '\n';
|
|
return 1;
|
|
}
|
|
RemoveVectoredExceptionHandler(handler);
|
|
}
|