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.
This commit is contained in:
Dean Galvin 2026-10-03 20:07:13 +13:00
parent 926906e257
commit a18cf0fc32
4 changed files with 36 additions and 1 deletions

View File

@ -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;

View File

@ -13,6 +13,7 @@ struct BdaTestAccess {
void RunBdaResourceTests(const AgcDriver::Graphics::Context& context, const BdaTestAccess& access);
void RunGuestAllocationTests();
void RunUnmappedGapTests();
void RunColorTargetLayoutTests();
#endif

View File

@ -1700,6 +1700,7 @@ int main() {
});
Require(mock.live == 0, "BDA resources leaked Vulkan objects");
RunGuestAllocationTests();
RunUnmappedGapTests();
RunColorTargetLayoutTests();
RunTextureFormatTests();
RunTextureTilingTests();

View File

@ -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 <cstring>
#include <array>
#include <algorithm>
#include <utility>
#include <vector>
#if defined(__linux__)
#include <sys/mman.h>
#include <unistd.h>
#endif
namespace {
@ -116,3 +123,22 @@ void RunGuestAllocationTests() {
}
#endif
}
void RunUnmappedGapTests() {
#if defined(__linux__)
namespace GuestMemory = AgcDriver::GuestMemory;
const auto page = static_cast<std::size_t>(sysconf(_SC_PAGESIZE));
auto* block = static_cast<std::byte*>(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<std::uint64_t>(block);
const auto ranges = GuestMemory::CommittedRanges(base, 3 * page);
const std::vector<std::pair<std::uint64_t, std::uint64_t>> 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<std::pair<std::uint64_t, std::uint64_t>>{{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<const void*>(std::uintptr_t{0x18}), 4), "a near-null address counts as accessible");
munmap(block, page);
munmap(block + 2 * page, page);
#endif
}