224 lines
12 KiB
C++
224 lines
12 KiB
C++
#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"
|
|
#include <cstring>
|
|
#include <atomic>
|
|
#include <exception>
|
|
#include <thread>
|
|
#include <array>
|
|
#include <algorithm>
|
|
#include <utility>
|
|
#include <vector>
|
|
#if defined(__linux__)
|
|
#include <sys/mman.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
namespace {
|
|
|
|
using AgcDriver::Graphics::Require;
|
|
|
|
template<typename TAction>
|
|
void reject(TAction action) {
|
|
try { action(); }
|
|
catch (const std::runtime_error&) { return; }
|
|
throw std::runtime_error("expected guest allocation ownership rejection");
|
|
}
|
|
|
|
}
|
|
|
|
void RunGuestLeaseWaitTests() {
|
|
std::array<std::byte, 128> memory{};
|
|
const auto address = reinterpret_cast<std::uintptr_t>(memory.data());
|
|
{
|
|
GuestAllocations::Mutation mutation;
|
|
mutation.Add(memory.data(), 64, true, true, true);
|
|
mutation.Add(memory.data() + 64, 64, true, true, true);
|
|
}
|
|
auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix();
|
|
std::erase_if(lease, [&](const auto& range) { return range->address != address; });
|
|
std::atomic<bool> mutationEntered = false;
|
|
std::atomic<bool> lookupFinished = false;
|
|
std::exception_ptr failure;
|
|
bool unmapped = false;
|
|
std::jthread mutator([&] {
|
|
try {
|
|
GuestAllocations::Mutation mutation;
|
|
mutationEntered.store(true);
|
|
mutationEntered.notify_one();
|
|
mutation.Unmap(memory.data(), 64, [&](const void*, std::size_t, const void*, bool) {
|
|
Require(lookupFinished.load(), "the allocation was unmapped before its lease holder finished");
|
|
unmapped = true;
|
|
});
|
|
} catch (...) { failure = std::current_exception(); }
|
|
});
|
|
mutationEntered.wait(false);
|
|
auto other = GuestAllocations::GuestAllocationsAcquire_nid_postfix();
|
|
std::erase_if(other, [&](const auto& range) { return range->address != address + 64; });
|
|
const bool found = other.size() == 1 && other.front()->address == address + 64;
|
|
lookupFinished.store(true);
|
|
lease.clear();
|
|
mutator.join();
|
|
other.clear();
|
|
{
|
|
GuestAllocations::Mutation mutation;
|
|
if (!unmapped) mutation.Remove(memory.data());
|
|
mutation.Remove(memory.data() + 64);
|
|
}
|
|
Require(found, "a lease holder could not query another allocation during an unmap wait");
|
|
if (failure) std::rethrow_exception(failure);
|
|
Require(unmapped, "waiting unmap did not resume after the lease was released");
|
|
}
|
|
|
|
void RunGuestAllocationTests() {
|
|
void* pointer = GuestHeap::GuestHeapAllocate_nid_postfix(32);
|
|
Require(reinterpret_cast<std::uintptr_t>(pointer) % alignof(std::max_align_t) == 0, "guest malloc is not suitably aligned");
|
|
std::memset(pointer, 0x55, 32);
|
|
{
|
|
auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix();
|
|
Require(lease.size() == 1 && lease.front()->address == reinterpret_cast<std::uintptr_t>(pointer), "guest heap registration is missing");
|
|
reject([&] { GuestHeap::GuestHeapFree_nid_postfix(pointer); });
|
|
reject([&] { GuestHeap::GuestHeapReallocate_nid_postfix(pointer, 64); });
|
|
GuestAllocations::Mutation mutation;
|
|
bool applied = false;
|
|
reject([&] { mutation.Protect(pointer, 32, true, false, true, [&] { applied = true; }); });
|
|
Require(!applied, "pinned guest protection changed");
|
|
}
|
|
pointer = GuestHeap::GuestHeapReallocate_nid_postfix(pointer, 64);
|
|
for (std::size_t i = 0; i < 32; ++i) Require(static_cast<unsigned char*>(pointer)[i] == 0x55, "guest realloc lost data");
|
|
GuestHeap::GuestHeapFree_nid_postfix(pointer);
|
|
pointer = GuestHeap::GuestHeapAlign_nid_postfix(4, 32);
|
|
Require(reinterpret_cast<std::uintptr_t>(pointer) % 4 == 0, "small guest alignment was not respected");
|
|
GuestHeap::GuestHeapFree_nid_postfix(pointer);
|
|
pointer = GuestHeap::GuestHeapAlign_nid_postfix(256, 32);
|
|
Require(reinterpret_cast<std::uintptr_t>(pointer) % 256 == 0, "guest aligned allocation lost alignment");
|
|
std::memset(pointer, 0x66, 32);
|
|
pointer = GuestHeap::GuestHeapReallocate_nid_postfix(pointer, 64);
|
|
Require(static_cast<unsigned char*>(pointer)[31] == 0x66, "aligned guest realloc lost data");
|
|
GuestHeap::GuestHeapFree_nid_postfix(pointer);
|
|
Require(GuestAllocations::GuestAllocationsAcquire_nid_postfix().empty(), "freed guest allocations remain registered");
|
|
std::array<std::byte, 128> mapping{};
|
|
{
|
|
GuestAllocations::Mutation mutation;
|
|
mutation.Add(mapping.data(), mapping.size(), true, true, true);
|
|
reject([&] { mutation.RequireAvailable(mapping.data() + 32, 16); });
|
|
mutation.Protect(mapping.data() + 32, 32, true, false, true, [] {});
|
|
}
|
|
{
|
|
const auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix();
|
|
Require(lease.size() == 3 && lease[0]->bytes == 32 && !lease[1]->writable && lease[2]->bytes == 64, "partial protection did not split the mapping");
|
|
GuestAllocations::Mutation mutation;
|
|
reject([&] { mutation.Unmap(mapping.data() + 32, 32, [](const void*, std::size_t, const void*, bool) {}); });
|
|
}
|
|
{
|
|
GuestAllocations::Mutation mutation;
|
|
bool applied = false;
|
|
mutation.Unmap(mapping.data() + 32, 32, [&](const void*, std::size_t, const void* allocation, bool last) {
|
|
Require(allocation == mapping.data() && !last, "partial unmap released the allocation");
|
|
applied = true;
|
|
});
|
|
Require(applied, "partial unmap callback was not called");
|
|
reject([&] { mutation.Protect(mapping.data(), mapping.size(), true, true, true, [] {}); });
|
|
mutation.Unmap(mapping.data(), 32, [&](const void*, std::size_t, const void* allocation, bool last) {
|
|
Require(allocation == mapping.data() && !last, "first fragment released remaining mapping");
|
|
});
|
|
mutation.Unmap(mapping.data() + 64, 64, [&](const void*, std::size_t, const void* allocation, bool last) {
|
|
Require(allocation == mapping.data() && last, "last fragment did not release the original allocation");
|
|
});
|
|
}
|
|
Require(GuestAllocations::GuestAllocationsAcquire_nid_postfix().empty(), "unmapped fragments remain registered");
|
|
#ifdef _WIN32
|
|
static std::byte imageProbe{};
|
|
{
|
|
GuestAllocations::Mutation mutation;
|
|
mutation.RegisterMainImage();
|
|
mutation.RegisterMainImage();
|
|
}
|
|
const auto imageAddress = reinterpret_cast<std::uintptr_t>(&imageProbe);
|
|
std::uint64_t allocationAddress = 0;
|
|
std::size_t imageRangeCount = 0;
|
|
{
|
|
const auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix();
|
|
imageRangeCount = lease.size();
|
|
const auto found = std::find_if(lease.begin(), lease.end(), [&](const auto& range) { return imageAddress >= range->address && imageAddress - range->address < range->bytes; });
|
|
Require(found != lease.end() && (*found)->writable && !(*found)->releasable, "main image registration is missing or releasable");
|
|
Require(std::none_of(lease.begin(), lease.end(), [](const auto& range) { return !range->releasable && range->gpu; }), "main image registered as GPU-mapped");
|
|
allocationAddress = (*found)->allocationAddress;
|
|
GuestAllocations::Mutation mutation;
|
|
bool applied = false;
|
|
reject([&] { mutation.Protect(&imageProbe, 1, true, false, true, [&] { applied = true; }); });
|
|
Require(!applied, "pinned image protection changed");
|
|
}
|
|
{
|
|
GuestAllocations::Mutation mutation;
|
|
reject([&] { mutation.Find(reinterpret_cast<void*>(allocationAddress)); });
|
|
reject([&] { mutation.Remove(reinterpret_cast<void*>(allocationAddress)); });
|
|
bool applied = false;
|
|
reject([&] { mutation.Unmap(&imageProbe, 1, [&](const void*, std::size_t, const void*, bool) { applied = true; }); });
|
|
Require(!applied, "image memory was unmapped");
|
|
reject([&] { mutation.Protect(&imageProbe, 1, true, false, true, [] { throw std::runtime_error("host protection failure"); }); });
|
|
}
|
|
Require(GuestAllocations::GuestAllocationsAcquire_nid_postfix().size() == imageRangeCount, "failed image protection changed registry ranges");
|
|
{
|
|
GuestAllocations::Mutation mutation;
|
|
mutation.Protect(&imageProbe, 1, true, true, true, [] {});
|
|
bool applied = false;
|
|
reject([&] { mutation.Unmap(&imageProbe, 1, [&](const void*, std::size_t, const void*, bool) { applied = true; }); });
|
|
Require(!applied, "split image memory became releasable");
|
|
}
|
|
#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);
|
|
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, true);
|
|
}
|
|
Require(GuestMemory::Accessible(guest, page, true), "a mapped arena page is not writable");
|
|
Require(mprotect(guest, page, PROT_NONE) == 0, "cannot revoke the arena test page behind the registry");
|
|
Require(GuestMemory::Accessible(guest, page, true), "a checked arena page was queried again instead of read from the page table");
|
|
Require(mprotect(guest, page, PROT_READ | PROT_WRITE) == 0, "cannot restore the arena test page behind the registry");
|
|
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, 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, true, [&] { 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
|
|
}
|