From a18cf0fc32f339b4ecffaff16c7221f5f9fcf10d Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Sat, 3 Oct 2026 20:07:13 +1300 Subject: [PATCH] fix(agc): describe the pages after a gap in a Linux host page query Without VirtualQuery, a range no cached page table covers is looked up line by line in /proc/self/maps. A cursor in a gap between mappings never matched a line, so the scan parsed the whole file and then reported the rest of the range unmapped, including mapped pages after the gap. The scan now stops at the first mapping above the cursor, reports the gap up to it as inaccessible and continues from there, as the VirtualQuery path does with a free region. PPSA21564 queries near-null addresses (0x18, 0x30, 0x58) about 1500 times a second during resource capture; each of those parsed the whole maps file (about 5700 lines here) instead of stopping at the first mapping. Tests: agc_driver_graphics_tests describes three host pages with the middle one unmapped as two ranges, a query that starts in the gap as the page after it, and a near-null address as inaccessible. --- .../Execution/src/GuestMemory.cpp | 9 ++++++- .../prx/libSceAgcDriver/tests/BdaTests.hpp | 1 + .../prx/libSceAgcDriver/tests/Graphics.cpp | 1 + .../tests/GuestAllocations.cpp | 26 +++++++++++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp index d8ca9bad0..39c938d28 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp @@ -542,7 +542,14 @@ bool describePages(std::uintptr_t address, std::size_t bytes, Emit&& emit) { char separator = 0; std::string permissions; if (!(fields >> std::hex >> first >> separator >> last >> permissions) || separator != '-' || first >= last || permissions.size() < 2) return false; - if (last <= cursor || first > cursor) continue; + if (last <= cursor) continue; + if (first > cursor) { + const auto gapEnd = std::min(end, first); + if (!emit(PageRun{cursor, gapEnd, false, false})) return true; + cursor = gapEnd; + found = true; + break; + } const auto next = std::min(end, last); if (!emit(PageRun{cursor, next, permissions[0] == 'r', permissions[0] == 'r' && permissions[1] == 'w'})) return true; cursor = next; diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaTests.hpp b/core/libs/prx/libSceAgcDriver/tests/BdaTests.hpp index 45d1d8f25..39aea69e4 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaTests.hpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaTests.hpp @@ -13,6 +13,7 @@ struct BdaTestAccess { void RunBdaResourceTests(const AgcDriver::Graphics::Context& context, const BdaTestAccess& access); void RunGuestAllocationTests(); +void RunUnmappedGapTests(); void RunColorTargetLayoutTests(); #endif diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index bb5e04d13..2b453967a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -1700,6 +1700,7 @@ int main() { }); Require(mock.live == 0, "BDA resources leaked Vulkan objects"); RunGuestAllocationTests(); + RunUnmappedGapTests(); RunColorTargetLayoutTests(); RunTextureFormatTests(); RunTextureTilingTests(); diff --git a/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp b/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp index 4d3f215aa..bb338d02e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp @@ -1,10 +1,17 @@ #include "BdaTests.hpp" #include "prx/libc/include/GuestAllocations.hpp" #include "prx/libc/include/GuestHeap.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" #include #include #include +#include +#include +#if defined(__linux__) +#include +#include +#endif namespace { @@ -116,3 +123,22 @@ void RunGuestAllocationTests() { } #endif } + +void RunUnmappedGapTests() { +#if defined(__linux__) + namespace GuestMemory = AgcDriver::GuestMemory; + const auto page = static_cast(sysconf(_SC_PAGESIZE)); + auto* block = static_cast(mmap(nullptr, 3 * page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(block != MAP_FAILED, "cannot map the gap test pages"); + Require(munmap(block + page, page) == 0, "cannot unmap the gap test's middle page"); + const auto base = reinterpret_cast(block); + const auto ranges = GuestMemory::CommittedRanges(base, 3 * page); + const std::vector> expected{{base, base + page}, {base + 2 * page, base + 3 * page}}; + Require(ranges == expected, "the pages after an unmapped gap in host memory were not described"); + Require(GuestMemory::CommittedRanges(base + page, 2 * page) == std::vector>{{base + 2 * page, base + 3 * page}}, "the pages after a query that starts in a gap were not described"); + Require(!GuestMemory::Accessible(block, 3 * page) && GuestMemory::Accessible(block + 2 * page, page, true), "an unmapped gap in host memory is misreported"); + 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); +#endif +}