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 <noreply@anthropic.com>
This commit is contained in:
Ahir 2026-10-09 02:00:05 +05:30 committed by Dean Galvin
parent 9f06176334
commit 49e9298706
2 changed files with 35 additions and 4 deletions

View File

@ -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<std::uintptr_t>(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<std::uintptr_t>(query.vma_start);
const auto regionEnd = static_cast<std::uintptr_t>(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<std::uintptr_t>(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;

View File

@ -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<const void*>(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<std::byte*>(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<void*>(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
}