From 49e9298706212d0972197d09d7434f0113224b06 Mon Sep 17 00:00:00 2001 From: Ahir <191682168+ahir061@users.noreply.github.com> Date: Fri, 9 Oct 2026 02:00:05 +0530 Subject: [PATCH] fix(agc): store only the requested pages of a Linux page query describePages stored the whole VMA that PROCMAP_QUERY or /proc/self/maps returned. Linux merges adjacent anonymous mappings with equal flags, so an arena VMA can span GiBs, and the first table miss inside it after an invalidation rewrote every page of it. Only the pages of the request, rounded out to whole pages, are stored now, as #1791 describes. When the guest arena is available, RunUnmappedGapTests checks that a check stores no pages outside its range, and that protecting and unmapping through the registry clears the stored pages. The registry checks follow the test on the perf/linux-page-cache branch linked from #1791. Co-Authored-By: Claude Opus 5.5 --- .../Execution/src/GuestMemory.cpp | 10 ++++--- .../tests/GuestAllocations.cpp | 29 +++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp index 77e3ac3e9..b884243b9 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp @@ -550,6 +550,10 @@ bool describePages(std::uintptr_t address, std::size_t bytes, Emit&& emit) { cursor = next; #else const auto generation = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + const auto recordRun = [&](std::uintptr_t next, bool readable, bool writable) { + constexpr auto mask = static_cast(PageBytes - 1); + pages.record(cursor & ~mask, (next + mask) & ~mask, readable, writable, generation); + }; if (const int fd = HostMapping::QueryFd(); fd >= 0) { procmap_query query{}; query.size = sizeof(query); @@ -563,12 +567,10 @@ bool describePages(std::uintptr_t address, std::size_t bytes, Emit&& emit) { cursor = gapEnd; continue; } - const auto base = static_cast(query.vma_start); - const auto regionEnd = static_cast(query.vma_end); const bool readable = (query.vma_flags & PROCMAP_QUERY_VMA_READABLE) != 0; const bool writable = readable && (query.vma_flags & PROCMAP_QUERY_VMA_WRITABLE) != 0; - pages.record(base, regionEnd, readable, writable, generation); const auto next = std::min(end, query.vma_end); + recordRun(next, readable, writable); if (!emit(PageRun{cursor, next, readable, writable})) return true; cursor = next; continue; @@ -600,8 +602,8 @@ bool describePages(std::uintptr_t address, std::size_t bytes, Emit&& emit) { } const bool readable = permissions[0] == 'r'; const bool writable = readable && permissions[1] == 'w'; - pages.record(first, last, readable, writable, generation); const auto next = std::min(end, last); + recordRun(next, readable, writable); if (!emit(PageRun{cursor, next, readable, writable})) return true; cursor = next; found = true; diff --git a/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp b/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp index f4f02db6a..de79f6a57 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp @@ -1,5 +1,6 @@ #include "BdaTests.hpp" #include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/GuestHeap.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" @@ -187,5 +188,33 @@ void RunUnmappedGapTests() { Require(!GuestMemory::Accessible(reinterpret_cast(std::uintptr_t{0x18}), 4), "a near-null address counts as accessible"); munmap(block, page); munmap(block + 2 * page, page); + if (!GuestArena::GuestArenaAvailable_nid_postfix()) return; + auto* guest = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(2 * page, page)); + Require(mmap(guest, 2 * page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) == guest, "cannot map the arena page test pages"); + { + GuestAllocations::Mutation mutation; + mutation.Add(guest, 2 * page, true, true); + } + Require(GuestMemory::Accessible(guest, page, true), "a mapped arena page is not writable"); + Require(mprotect(guest + page, page, PROT_READ) == 0, "cannot protect the arena test's second page"); + Require(!GuestMemory::Accessible(guest + page, page, true), "a check stored arena pages outside its range"); + { + GuestAllocations::Mutation mutation; + mutation.Protect(guest + page, page, true, true, [&] { Require(mprotect(guest + page, page, PROT_READ | PROT_WRITE) == 0, "cannot restore the arena test's second page"); }); + } + Require(GuestMemory::Accessible(guest, 2 * page, true), "mapped arena pages are not writable"); + { + GuestAllocations::Mutation mutation; + mutation.Protect(guest, page, true, false, [&] { Require(mprotect(guest, page, PROT_READ) == 0, "cannot protect the arena test page"); }); + } + Require(!GuestMemory::Accessible(guest, page, true) && GuestMemory::Accessible(guest, page) && GuestMemory::Accessible(guest + page, page, true), "an arena page protected through the registry kept its old access"); + { + GuestAllocations::Mutation mutation; + const auto release = [](const void* pointer, std::size_t bytes, const void*, bool) { Require(munmap(const_cast(pointer), bytes) == 0, "cannot unmap an arena test page"); }; + mutation.Unmap(guest, page, release); + mutation.Unmap(guest + page, page, release); + } + Require(!GuestMemory::Accessible(guest, page) && !GuestMemory::Accessible(guest + page, page), "unmapped arena pages are still accessible"); + GuestArena::GuestArenaRelease_nid_postfix(guest, 2 * page); #endif }