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:
parent
9f06176334
commit
49e9298706
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue