mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-22 03:46:53 +02:00
Rework guest memory tracking/virtual address space/direct and flexible memory (#135)
* Rework guest memory tracking * add unknwon flag * Fix macOS guest address-space reservation
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
#include "graphics/host_gpu/memoryTracker.h"
|
||||
#include "graphics/host_gpu/rangeSet.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/virtualMemory.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
@@ -209,6 +210,20 @@ class SharedPage final {
|
||||
HANDLE mapping_ = nullptr;
|
||||
};
|
||||
#endif
|
||||
|
||||
bool ProtectAddressSpace(uint64_t vaddr, uint64_t size,
|
||||
Common::VirtualMemory::Mode mode) {
|
||||
uint32_t protection = PAGE_NOACCESS;
|
||||
if (mode == Common::VirtualMemory::Mode::Read) {
|
||||
protection = PAGE_READONLY;
|
||||
} else if (mode == Common::VirtualMemory::Mode::ReadWrite) {
|
||||
protection = PAGE_READWRITE;
|
||||
}
|
||||
DWORD old_protection = 0;
|
||||
return VirtualProtect(reinterpret_cast<void *>(vaddr), size, protection,
|
||||
&old_protection) != 0;
|
||||
}
|
||||
|
||||
#if 1
|
||||
|
||||
bool DummyFault(void *, PageFaultAccess, uint64_t, uint64_t, PageFaultPhase) noexcept {
|
||||
@@ -353,7 +368,8 @@ struct DownloadTrackerHarness {
|
||||
return completed;
|
||||
}
|
||||
|
||||
DownloadTrackerHarness() : page_manager(Fault, this), tracker(page_manager) {}
|
||||
DownloadTrackerHarness()
|
||||
: page_manager(Fault, this), tracker(page_manager) {}
|
||||
|
||||
PageFaultAccess pending_access = PageFaultAccess::Unknown;
|
||||
uint64_t download_address = 0;
|
||||
@@ -366,11 +382,6 @@ struct DownloadTrackerHarness {
|
||||
|
||||
std::atomic<PageManager *> g_native_page_manager{nullptr};
|
||||
std::atomic_bool g_native_fault_entered{false};
|
||||
std::atomic_bool g_unmap_contended{false};
|
||||
|
||||
void UnmapContended() noexcept {
|
||||
g_unmap_contended.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
LONG CALLBACK NativeTrackerFaultHandler(EXCEPTION_POINTERS *exception) {
|
||||
@@ -552,7 +563,7 @@ void TestGpuDownloadFaultOwnership() {
|
||||
!harness.tracker.IsRegionGpuModified(address, page_size) &&
|
||||
harness.tracker.IsRegionCpuModified(address, page_size) && IsWritable(memory),
|
||||
"GPU write fault did not download before granting CPU ownership");
|
||||
harness.tracker.UnmapMemory(address, page_size);
|
||||
harness.tracker.UntrackMemory(address, page_size);
|
||||
}
|
||||
|
||||
void TestVirtualGpuWriteDiscard() {
|
||||
@@ -578,7 +589,7 @@ void TestVirtualGpuWriteDiscard() {
|
||||
Check(!harness.tracker.IsRegionGpuModified(address, page_size) &&
|
||||
harness.tracker.IsRegionCpuModified(address, page_size) && IsWritable(memory),
|
||||
"virtual GPU discard did not transfer the page to CPU ownership");
|
||||
harness.tracker.UnmapMemory(address, page_size);
|
||||
harness.tracker.UntrackMemory(address, page_size);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
@@ -684,6 +695,15 @@ void TestRangeSet() {
|
||||
"range set subtraction did not preserve both exact tails");
|
||||
}
|
||||
|
||||
void TestQueriesDoNotRequireMappedOwnership() {
|
||||
constexpr uint64_t address = 0x0000000203000000ull;
|
||||
TrackerHarness harness;
|
||||
const auto page_size = harness.page_manager.GetPageSize();
|
||||
Check(harness.tracker.IsRegionCpuModified(address, page_size) &&
|
||||
!harness.tracker.IsRegionGpuModified(address, page_size),
|
||||
"unowned tracker range did not expose its initial CPU-dirty state");
|
||||
}
|
||||
|
||||
void TestRangeInvalidation() {
|
||||
constexpr uintptr_t base = 0x0000000201000000ull;
|
||||
TrackerHarness harness;
|
||||
@@ -767,7 +787,7 @@ void TestCpuDirtyUploadAndFault() {
|
||||
Check(IsWritable(memory),
|
||||
"explicit CPU dirty transition did not release the rearmed watch");
|
||||
|
||||
tracker.UnmapMemory(address, page_size * 2);
|
||||
tracker.UntrackMemory(address, page_size * 2);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
@@ -795,7 +815,7 @@ void TestFaultDuringUploadRemainsDirty() {
|
||||
});
|
||||
Check(tracker.IsRegionCpuModified(address, page_size) && IsWritable(memory),
|
||||
"upload completion erased a racing CPU dirty transition");
|
||||
tracker.UnmapMemory(address, page_size);
|
||||
tracker.UntrackMemory(address, page_size);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
@@ -840,7 +860,7 @@ void TestNativeStoreDuringRangeEnumeration() {
|
||||
IsWritable(memory),
|
||||
"native store during range enumeration was lost");
|
||||
|
||||
tracker.UnmapMemory(address, page_size);
|
||||
tracker.UntrackMemory(address, page_size);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
@@ -924,7 +944,7 @@ void TestFaultDuringDownloadSynchronization() {
|
||||
page_manager.IsTracked(address + page_size * 2),
|
||||
"uncontended dirty page did not retain its clean write watch");
|
||||
|
||||
tracker.UnmapMemory(address, page_size * 3);
|
||||
tracker.UntrackMemory(address, page_size * 3);
|
||||
}
|
||||
|
||||
void TestFaultAndExplicitDirtyRace() {
|
||||
@@ -964,7 +984,7 @@ void TestFaultAndExplicitDirtyRace() {
|
||||
IsWritable(memory),
|
||||
"fault/explicit-dirty race lost dirty state or write access");
|
||||
}
|
||||
tracker.UnmapMemory(address, page_size);
|
||||
tracker.UntrackMemory(address, page_size);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
@@ -1016,7 +1036,7 @@ void TestSharedTrackersAndConcurrentPageFaults() {
|
||||
}
|
||||
|
||||
harness.first.UntrackMemory(address, page_size * 2);
|
||||
harness.second.UnmapMemory(address, page_size * 2);
|
||||
harness.second.UntrackMemory(address, page_size * 2);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
@@ -1049,7 +1069,7 @@ void TestGpuDirtyBits() {
|
||||
"explicit GPU dirty transition did not trap CPU access");
|
||||
tracker.UnmarkRegionAsGpuModified(address, page_size);
|
||||
tracker.MarkRegionAsCpuModified(address, page_size);
|
||||
tracker.UnmapMemory(address, page_size * 2);
|
||||
tracker.UntrackMemory(address, page_size * 2);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
@@ -1084,7 +1104,7 @@ void TestCrossRegionUpload() {
|
||||
"cross-region written upload did not mark GPU dirty state");
|
||||
tracker.UnmarkRegionAsGpuModified(boundary - page_size, page_size * 2);
|
||||
tracker.MarkRegionAsCpuModified(boundary - page_size, page_size * 2);
|
||||
tracker.UnmapMemory(address, region_size * 2);
|
||||
tracker.UntrackMemory(address, region_size * 2);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
@@ -1094,9 +1114,6 @@ void TestCrossRegionUpload() {
|
||||
auto &tracker = harness.tracker;
|
||||
auto &page_manager = harness.page_manager;
|
||||
const auto page_size = page_manager.GetPageSize();
|
||||
if (std::strcmp(name, "unmapped") == 0) {
|
||||
(void)tracker.IsRegionCpuModified(base, page_size);
|
||||
}
|
||||
const auto allocation_size =
|
||||
std::strcmp(name, "missing-download-bytes") == 0 ? page_size * 2
|
||||
: page_size;
|
||||
@@ -1149,20 +1166,6 @@ void TestCrossRegionUpload() {
|
||||
}
|
||||
});
|
||||
fault.join();
|
||||
} else if (std::strcmp(name, "gpu-dirty-unmap-race") == 0) {
|
||||
g_unmap_contended.store(false, std::memory_order_release);
|
||||
MemoryTracker::SetUnmapContentionHook(UnmapContended);
|
||||
std::thread unmap;
|
||||
tracker.ForEachUploadRange(
|
||||
address, page_size, true, [](uint64_t, uint64_t) noexcept {},
|
||||
[&]() noexcept {
|
||||
unmap = std::thread(
|
||||
[&] { tracker.UnmapMemory(address, page_size); });
|
||||
while (!g_unmap_contended.load(std::memory_order_acquire)) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
});
|
||||
unmap.join();
|
||||
} else if (std::strcmp(name, "missing-download-bytes") == 0) {
|
||||
tracker.ForEachUploadRange(
|
||||
address, allocation_size, true, [](uint64_t, uint64_t) noexcept {},
|
||||
@@ -1193,8 +1196,7 @@ void TestFatalPaths() {
|
||||
#endif
|
||||
for (const char *name : {"gpu-dirty-fault", "gpu-dirty-read", "virtual-gpu-read",
|
||||
"gpu-dirty-explicit-cpu",
|
||||
"unmapped", "reentrant-upload",
|
||||
"writable-upload-race", "gpu-dirty-unmap-race",
|
||||
"reentrant-upload", "writable-upload-race",
|
||||
"missing-download-bytes"}) {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
std::string command = std::string("\"") + path + "\" --death " + name;
|
||||
@@ -1236,6 +1238,14 @@ void TestFatalPaths() {
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Libs::LibKernel::Memory {
|
||||
|
||||
bool ProtectGuestHostMemory(uint64_t vaddr, uint64_t size, Common::VirtualMemory::Mode mode) {
|
||||
return ProtectAddressSpace(vaddr, size, mode);
|
||||
}
|
||||
|
||||
} // namespace Libs::LibKernel::Memory
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
#if 1
|
||||
if (argc == 3 && std::strcmp(argv[1], "--death") == 0) {
|
||||
@@ -1249,6 +1259,7 @@ int main(int argc, char **argv) {
|
||||
TestSameSlabTrackerArbitration();
|
||||
TestSharedMetadataAndImagePageFault();
|
||||
TestRangeSet();
|
||||
TestQueriesDoNotRequireMappedOwnership();
|
||||
TestRangeInvalidation();
|
||||
TestGpuDirtyBits();
|
||||
TestCrossRegionUpload();
|
||||
|
||||
+32
-85
@@ -1,4 +1,5 @@
|
||||
#include "graphics/host_gpu/pageManager.h"
|
||||
#include "common/virtualMemory.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
@@ -28,7 +29,6 @@
|
||||
|
||||
namespace {
|
||||
|
||||
using Libs::Graphics::GpuAccess;
|
||||
using Libs::Graphics::PageFaultAccess;
|
||||
using Libs::Graphics::PageManager;
|
||||
|
||||
@@ -122,6 +122,23 @@ uint32_t Protection(const void *address) {
|
||||
return info.Protect;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::atomic_uint64_t g_protection_calls{0};
|
||||
|
||||
bool ProtectAddressSpace(uint64_t vaddr, uint64_t size,
|
||||
Common::VirtualMemory::Mode mode) {
|
||||
uint32_t protection = PAGE_NOACCESS;
|
||||
if (mode == Common::VirtualMemory::Mode::Read) {
|
||||
protection = PAGE_READONLY;
|
||||
} else if (mode == Common::VirtualMemory::Mode::ReadWrite) {
|
||||
protection = PAGE_READWRITE;
|
||||
}
|
||||
DWORD old_protection = 0;
|
||||
g_protection_calls.fetch_add(1, std::memory_order_relaxed);
|
||||
return VirtualProtect(reinterpret_cast<void *>(vaddr), size, protection,
|
||||
&old_protection) != 0;
|
||||
}
|
||||
|
||||
#if 1
|
||||
|
||||
struct FaultContext {
|
||||
@@ -254,6 +271,7 @@ uint8_t *Allocate(uint64_t size, uint32_t protection = PAGE_READWRITE) {
|
||||
}
|
||||
|
||||
void TestWatchFaultAndUnwatch() {
|
||||
g_protection_calls.store(0, std::memory_order_relaxed);
|
||||
FaultContext context;
|
||||
PageManager manager(InvalidateFault, &context);
|
||||
context.manager = &manager;
|
||||
@@ -266,6 +284,8 @@ void TestWatchFaultAndUnwatch() {
|
||||
Check(manager.IsTracked(reinterpret_cast<uint64_t>(memory)) &&
|
||||
!IsWritable(memory),
|
||||
"watch did not protect the page");
|
||||
Check(g_protection_calls.load(std::memory_order_relaxed) != 0,
|
||||
"watch protection bypassed the address-space owner callback");
|
||||
Check(manager.HandleFault(PageFaultAccess::Write,
|
||||
reinterpret_cast<uint64_t>(memory + 32)),
|
||||
"tracked write fault was not handled");
|
||||
@@ -343,14 +363,6 @@ void TestPermittedMappedLateFaultsResume() {
|
||||
"second delayed mapped write was not accepted");
|
||||
Check(manager.HandleFault(PageFaultAccess::Read, address),
|
||||
"delayed mapped read was not accepted on readable backing");
|
||||
DWORD old_protection = 0;
|
||||
Check(VirtualProtect(memory, page_size, PAGE_READONLY, &old_protection) != 0 &&
|
||||
old_protection == PAGE_READWRITE,
|
||||
"failed to prepare intentional read-only protection");
|
||||
Check(!manager.HandleFault(PageFaultAccess::Write, address),
|
||||
"intentional read-only mapping accepted a write fault");
|
||||
Check(VirtualProtect(memory, page_size, PAGE_READWRITE, &old_protection) != 0,
|
||||
"failed to restore writable protection");
|
||||
manager.OnGpuUnmap(address, page_size);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
@@ -496,31 +508,6 @@ void TestNativeAccessViolation() {
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
void TestInvalidLateWriteTokenIsConsumed() {
|
||||
FaultContext context;
|
||||
PageManager manager(InvalidateFault, &context);
|
||||
context.manager = &manager;
|
||||
const auto page_size = manager.GetPageSize();
|
||||
auto *memory = Allocate(page_size);
|
||||
const auto address = reinterpret_cast<uint64_t>(memory);
|
||||
manager.OnGpuMap(address, page_size);
|
||||
manager.UpdatePageWatchers(true, address, page_size);
|
||||
Check(manager.HandleFault(PageFaultAccess::Write, address),
|
||||
"initial write fault was not handled");
|
||||
DWORD old_protection = 0;
|
||||
Check(VirtualProtect(memory, page_size, PAGE_READONLY, &old_protection) !=
|
||||
0 &&
|
||||
old_protection == PAGE_READWRITE,
|
||||
"failed to create invalid late-write protection state");
|
||||
Check(!manager.HandleFault(PageFaultAccess::Write, address) &&
|
||||
!manager.HandleFault(PageFaultAccess::Write, address),
|
||||
"invalid late-write token was accepted or retained");
|
||||
Check(VirtualProtect(memory, page_size, PAGE_READWRITE, &old_protection) != 0,
|
||||
"failed to restore test protection");
|
||||
manager.OnGpuUnmap(address, page_size);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
void TestCrossRegionRange() {
|
||||
FaultContext context;
|
||||
PageManager manager(InvalidateFault, &context);
|
||||
@@ -627,9 +614,7 @@ void TestBatchedWatcherRanges() {
|
||||
manager->UpdatePageWatchers(false, 0x1000, page_size);
|
||||
} else {
|
||||
const bool two_pages = std::strcmp(name, "cross-reentrant") == 0;
|
||||
auto *memory = Allocate(
|
||||
two_pages ? page_size * 2 : page_size,
|
||||
std::strcmp(name, "protection") == 0 ? PAGE_READONLY : PAGE_READWRITE);
|
||||
auto *memory = Allocate(two_pages ? page_size * 2 : page_size);
|
||||
const auto address = reinterpret_cast<uint64_t>(memory);
|
||||
manager->OnGpuMap(address, two_pages ? page_size * 2 : page_size);
|
||||
manager->UpdatePageWatchers(true, address, page_size);
|
||||
@@ -659,9 +644,7 @@ void TestBatchedWatcherRanges() {
|
||||
}
|
||||
(void)manager->HandleFault(PageFaultAccess::Read, address);
|
||||
first.join();
|
||||
} else if (std::strcmp(name, "watched-unmap") == 0) {
|
||||
manager->OnGpuUnmap(address, page_size);
|
||||
} else if (std::strcmp(name, "protection") != 0) {
|
||||
} else {
|
||||
std::_Exit(0x7f);
|
||||
}
|
||||
}
|
||||
@@ -712,7 +695,7 @@ void TestFatalPaths() {
|
||||
for (const char *name :
|
||||
{"invalid-range", "unknown-untrack", "destructor-watch", "non-write",
|
||||
"callback-false", "reentrant", "cross-reentrant",
|
||||
"concurrent-non-write", "watched-unmap", "protection"}) {
|
||||
"concurrent-non-write"}) {
|
||||
CheckDeathCase(name);
|
||||
}
|
||||
}
|
||||
@@ -773,51 +756,18 @@ void TestExternalDirtyTransferDuringResolution() {
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
void TestMappingDoesNotRequireCpuWriteAccess() {
|
||||
FaultContext context;
|
||||
PageManager manager(InvalidateFault, &context);
|
||||
context.manager = &manager;
|
||||
const auto page_size = manager.GetPageSize();
|
||||
auto *memory = Allocate(page_size);
|
||||
DWORD old_protection = 0;
|
||||
Check(VirtualProtect(memory, page_size, PAGE_NOACCESS, &old_protection) != 0 &&
|
||||
old_protection == PAGE_READWRITE,
|
||||
"failed to prepare CPU-inaccessible mapping");
|
||||
const auto address = reinterpret_cast<uint64_t>(memory);
|
||||
manager.OnGpuMap(address, page_size);
|
||||
Check(manager.IsMapped(address, page_size),
|
||||
"CPU-inaccessible committed range was not GPU mapped");
|
||||
manager.OnGpuUnmap(address, page_size);
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
|
||||
void TestGpuAccessPermissions() {
|
||||
FaultContext context;
|
||||
PageManager manager(InvalidateFault, &context);
|
||||
context.manager = &manager;
|
||||
const auto page_size = manager.GetPageSize();
|
||||
auto *memory = Allocate(page_size);
|
||||
const auto address = reinterpret_cast<uint64_t>(memory);
|
||||
manager.OnGpuMap(address, page_size, GpuAccess::Read);
|
||||
Check(manager.HasGpuAccess(address, page_size, GpuAccess::Read) &&
|
||||
!manager.HasGpuAccess(address, page_size, GpuAccess::Write),
|
||||
"read-only GPU mapping granted write access");
|
||||
manager.OnGpuMap(address, page_size, GpuAccess::Write);
|
||||
Check(manager.HasGpuAccess(address, page_size, GpuAccess::ReadWrite),
|
||||
"overlapping GPU mappings did not combine permissions");
|
||||
manager.OnGpuUnmap(address, page_size, GpuAccess::Read);
|
||||
Check(!manager.HasGpuAccess(address, page_size, GpuAccess::Read) &&
|
||||
manager.HasGpuAccess(address, page_size, GpuAccess::Write),
|
||||
"GPU read unmap removed the wrong permission");
|
||||
manager.OnGpuUnmap(address, page_size, GpuAccess::Write);
|
||||
Check(!manager.IsMapped(address, page_size),
|
||||
"GPU permission mappings were not fully balanced");
|
||||
Check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Libs::LibKernel::Memory {
|
||||
|
||||
bool ProtectGuestHostMemory(uint64_t vaddr, uint64_t size, Common::VirtualMemory::Mode mode) {
|
||||
return ProtectAddressSpace(vaddr, size, mode);
|
||||
}
|
||||
|
||||
} // namespace Libs::LibKernel::Memory
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
#if 1
|
||||
if (argc == 3 && std::strcmp(argv[1], "--death") == 0) {
|
||||
@@ -831,13 +781,10 @@ int main(int argc, char **argv) {
|
||||
TestNativeDelayedReadAfterModeDowngrade();
|
||||
TestDelayedFaultAfterExplicitUnwatch();
|
||||
TestNativeAccessViolation();
|
||||
TestInvalidLateWriteTokenIsConsumed();
|
||||
TestCrossRegionRange();
|
||||
TestBatchedWatcherRanges();
|
||||
TestConcurrentFault();
|
||||
TestExternalDirtyTransferDuringResolution();
|
||||
TestMappingDoesNotRequireCpuWriteAccess();
|
||||
TestGpuAccessPermissions();
|
||||
TestFatalPaths();
|
||||
std::puts("PageManagerTests: all cases passed");
|
||||
return 0;
|
||||
|
||||
@@ -1274,6 +1274,59 @@ public:
|
||||
std::printf("[host] %-32s ok\n", "SchedulerTimeline");
|
||||
}
|
||||
|
||||
void CheckGpuMappedRangeLifecycle() {
|
||||
EnsureRuntimeContext();
|
||||
CommandScheduler scheduler(Renderer(), m_runtime_context);
|
||||
HW::Context registers{};
|
||||
HW::UserConfig user_config{};
|
||||
HW::Shader shaders{};
|
||||
scheduler.Begin(registers, user_config, shaders);
|
||||
Gpu gpu(Renderer());
|
||||
GpuResourceManager resources(m_runtime_context, scheduler);
|
||||
resources.SetGpu(&gpu);
|
||||
|
||||
constexpr uint64_t base = 0x0000000200000000ull;
|
||||
constexpr uint64_t page = 0x4000;
|
||||
resources.MapMemory(base, page * 4);
|
||||
resources.MapMemory(base + page * 2, page * 4);
|
||||
Require("GpuMappedRangeLifecycle", "union",
|
||||
resources.IsMapped(base, page * 6) &&
|
||||
!resources.IsMapped(base, page * 7),
|
||||
"overlapping maps did not form one interval union");
|
||||
|
||||
resources.UnmapMemory(base + page * 2, page * 2);
|
||||
Require("GpuMappedRangeLifecycle", "subtract",
|
||||
resources.IsMapped(base, page * 2) &&
|
||||
resources.IsMapped(base + page * 4, page * 2) &&
|
||||
!resources.IsMapped(base, page * 6),
|
||||
"partial unmap did not punch the expected interval hole");
|
||||
|
||||
resources.UnmapMemory(base + page * 2, page * 2);
|
||||
Require("GpuMappedRangeLifecycle", "idempotent unmap",
|
||||
resources.IsMapped(base, page * 2) &&
|
||||
resources.IsMapped(base + page * 4, page * 2),
|
||||
"unmapping an absent interval changed neighboring mappings");
|
||||
|
||||
resources.UnmapMemory(base, page * 6);
|
||||
Require("GpuMappedRangeLifecycle", "clear",
|
||||
!resources.IsMapped(base, page * 6),
|
||||
"full unmap did not clear the interval union");
|
||||
|
||||
constexpr uint64_t old_prt = base + page * 8;
|
||||
constexpr uint64_t new_prt = base + page * 16;
|
||||
resources.MapMemory(old_prt, page * 4);
|
||||
resources.UnmapMemory(old_prt, page * 4);
|
||||
resources.MapMemory(new_prt, page * 6);
|
||||
Require("GpuMappedRangeLifecycle", "PRT replacement",
|
||||
!resources.IsMapped(old_prt, page * 4) &&
|
||||
resources.IsMapped(new_prt, page * 6),
|
||||
"old-unmap/new-map did not replace full PRT coverage");
|
||||
|
||||
resources.SetGpu(nullptr);
|
||||
scheduler.Finish();
|
||||
std::printf("[host] %-32s ok\n", "GpuMappedRangeLifecycle");
|
||||
}
|
||||
|
||||
void CheckStreamBufferRing() {
|
||||
EnsureRuntimeContext();
|
||||
CommandScheduler scheduler(Renderer(), m_runtime_context);
|
||||
@@ -1526,7 +1579,7 @@ public:
|
||||
fault_memory == reinterpret_cast<void *>(fault_base),
|
||||
"fixed processor-fault allocation failed");
|
||||
auto &resources = context.GetGpuResources();
|
||||
resources.MapMemory(fault_base, fault_size, GpuAccess::ReadWrite);
|
||||
resources.MapMemory(fault_base, fault_size);
|
||||
|
||||
constexpr uint64_t immediate_dst = fault_base + 0x1000;
|
||||
constexpr uint64_t immediate_memory_dst = fault_base + 0x2000;
|
||||
@@ -1683,7 +1736,7 @@ public:
|
||||
resources.InvalidateMemory(fault_base, sizeof(uint32_t)),
|
||||
"processor memory invalidation did not find its mapped range");
|
||||
});
|
||||
resources.UnmapMemory(fault_base, fault_size, GpuAccess::ReadWrite);
|
||||
resources.UnmapMemory(fault_base, fault_size);
|
||||
Require("GpuCommandLane", "processor fault unmap",
|
||||
Libs::LibKernel::Memory::KernelMunmap(fault_base, fault_size) == 0,
|
||||
"processor-fault direct-memory mapping release failed");
|
||||
@@ -1953,7 +2006,7 @@ public:
|
||||
GpuResourceManager resources(m_runtime_context, scheduler);
|
||||
resources.SetGpu(&gpu);
|
||||
auto &cache = resources.GetBufferCache();
|
||||
resources.MapMemory(base, allocation_size, GpuAccess::ReadWrite);
|
||||
resources.MapMemory(base, allocation_size);
|
||||
|
||||
const auto MarkGpuWrite = [&](uint64_t address, uint64_t size) {
|
||||
auto allocation =
|
||||
@@ -2376,7 +2429,7 @@ public:
|
||||
sizeof(reacquire_value));
|
||||
|
||||
resources.SetGpu(nullptr);
|
||||
resources.UnmapMemory(base, allocation_size, GpuAccess::ReadWrite);
|
||||
resources.UnmapMemory(base, allocation_size);
|
||||
scheduler.Finish();
|
||||
}
|
||||
gpu.Shutdown();
|
||||
@@ -2431,7 +2484,7 @@ public:
|
||||
narrow_download != nullptr && narrow_download_offset % 4 == 0 &&
|
||||
wide_download != nullptr && wide_download_offset % 16 == 0,
|
||||
"wide/block image readback was not aligned to its texel block");
|
||||
resources.MapMemory(base, allocation_size, GpuAccess::ReadWrite);
|
||||
resources.MapMemory(base, allocation_size);
|
||||
|
||||
ImageDesc sampled{};
|
||||
sampled.type = BindingType::Texture;
|
||||
@@ -4834,7 +4887,7 @@ public:
|
||||
m_device.destroyShaderModule(ms_depth_module, nullptr);
|
||||
|
||||
resources.SetGpu(nullptr);
|
||||
resources.UnmapMemory(base, allocation_size, GpuAccess::ReadWrite);
|
||||
resources.UnmapMemory(base, allocation_size);
|
||||
scheduler.Finish();
|
||||
}
|
||||
gpu.Shutdown();
|
||||
@@ -4879,7 +4932,7 @@ public:
|
||||
scheduler.Begin(registers, user_config, shaders);
|
||||
{
|
||||
GpuResourceManager resources(m_runtime_context, scheduler);
|
||||
resources.MapMemory(base, allocation_size, GpuAccess::ReadWrite);
|
||||
resources.MapMemory(base, allocation_size);
|
||||
const uint32_t pitch = TileGetTexturePitch(format, 1, 1, tile);
|
||||
TileSizeAlign total{};
|
||||
TileSizeOffset mip{};
|
||||
@@ -4978,7 +5031,7 @@ public:
|
||||
std::vector<u32>{0x40004200u, 0x44003c00u},
|
||||
"tiled BGRA16 Buffer mirror changed guest component order");
|
||||
DestroyBuffer(&mirror_readback);
|
||||
resources.UnmapMemory(base, allocation_size, GpuAccess::ReadWrite);
|
||||
resources.UnmapMemory(base, allocation_size);
|
||||
scheduler.Finish();
|
||||
}
|
||||
Require(name, "unmap",
|
||||
@@ -5025,7 +5078,7 @@ public:
|
||||
auto &resources = context.GetGpuResources();
|
||||
auto &texture_cache = resources.GetTextureCache();
|
||||
auto &executor = context.GetRenderExecutor();
|
||||
resources.MapMemory(base, allocation_size, GpuAccess::ReadWrite);
|
||||
resources.MapMemory(base, allocation_size);
|
||||
|
||||
constexpr auto stencil_format =
|
||||
Prospero::GpuEnumValue(Prospero::BufferFormat::k8UInt);
|
||||
@@ -6011,10 +6064,8 @@ public:
|
||||
const auto stale_ordered_color =
|
||||
texture_cache.FindImage(ordered_color_desc);
|
||||
RenderExecutorTestAccess::BindRenderTarget(executor, stale_ordered_color);
|
||||
resources.UnmapMemory(ordered_color_address, target_mip_size,
|
||||
GpuAccess::ReadWrite);
|
||||
resources.MapMemory(ordered_color_address, target_mip_size,
|
||||
GpuAccess::ReadWrite);
|
||||
resources.UnmapMemory(ordered_color_address, target_mip_size);
|
||||
resources.MapMemory(ordered_color_address, target_mip_size);
|
||||
|
||||
auto ordered_depth_desc = depth;
|
||||
ordered_depth_desc.info.stencil = {ordered_color_address,
|
||||
@@ -6167,7 +6218,7 @@ public:
|
||||
texture_cache.GetImage(depth_id).usage.storage,
|
||||
"storage stencil binding did not acquire the associated depth owner");
|
||||
RenderExecutorTestAccess::ResetBindings(executor);
|
||||
resources.UnmapMemory(base, allocation_size, GpuAccess::ReadWrite);
|
||||
resources.UnmapMemory(base, allocation_size);
|
||||
scheduler.Finish();
|
||||
}
|
||||
|
||||
@@ -17198,12 +17249,12 @@ void CheckStandard64RenderTargetTileRoundTrip() {
|
||||
void CheckStorageTextureGpuOwnedRebindState() {
|
||||
constexpr uintptr_t base = 0x0000000200200000ull;
|
||||
constexpr uint64_t size = 0x10000;
|
||||
auto *memory = static_cast<uint8_t *>(
|
||||
VirtualAlloc(reinterpret_cast<void *>(base), size,
|
||||
MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
|
||||
const auto guest_memory = Libs::LibKernel::Memory::AllocateRuntimeMemory(
|
||||
base, size, Common::VirtualMemory::Mode::ReadWrite,
|
||||
"storage_texture_gpu_owned_rebind", true);
|
||||
auto *memory = reinterpret_cast<uint8_t *>(guest_memory);
|
||||
Require("StorageTextureGpuOwnedRebind", "allocation",
|
||||
memory == reinterpret_cast<void *>(base),
|
||||
"fixed VirtualAlloc failed");
|
||||
guest_memory == base, "fixed guest-owner allocation failed");
|
||||
PageManager page_manager(CacheFault, nullptr);
|
||||
MemoryTracker tracker(page_manager);
|
||||
page_manager.OnGpuMap(base, size);
|
||||
@@ -17215,7 +17266,6 @@ void CheckStorageTextureGpuOwnedRebindState() {
|
||||
Require(
|
||||
"StorageTextureGpuOwnedRebind", "owned",
|
||||
tracker.IsRegionGpuModified(base, size) &&
|
||||
page_manager.IsMapped(base, size) &&
|
||||
(!HostMemoryQueryReadable(base, size, readable) || readable < size) &&
|
||||
HostMemoryQueryRange(base, size, HostMemoryAccess::Mapped, mapped) &&
|
||||
mapped == size &&
|
||||
@@ -17261,7 +17311,8 @@ void CheckStorageTextureGpuOwnedRebindState() {
|
||||
tracker.UntrackMemory(base, size);
|
||||
page_manager.OnGpuUnmap(base, size);
|
||||
Require("StorageTextureGpuOwnedRebind", "free",
|
||||
VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
Libs::LibKernel::Memory::FreeGuestMemory(base, size),
|
||||
"guest-owner free failed");
|
||||
std::printf("[host] %-32s ok\n", "StorageTextureGpuOwnedRebind");
|
||||
}
|
||||
#endif
|
||||
@@ -18029,6 +18080,11 @@ int main(int argc, char **argv) {
|
||||
vulkan.CheckSchedulerTimeline();
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--mapped-range-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
vulkan.CheckGpuMappedRangeLifecycle();
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--stream-buffer-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
vulkan.CheckStreamBufferRing();
|
||||
@@ -18184,6 +18240,7 @@ int main(int argc, char **argv) {
|
||||
CheckEmbeddedFetchLaneSpill();
|
||||
CheckPs5GameExampleImageClearRuntimeShape();
|
||||
vulkan.CheckSchedulerTimeline();
|
||||
vulkan.CheckGpuMappedRangeLifecycle();
|
||||
vulkan.CheckStreamBufferRing();
|
||||
vulkan.CheckCommandPoolGrowth();
|
||||
vulkan.CheckGpuTilerCpuParity();
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
#include "common/commonSubsystem.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/subsystems.h"
|
||||
#include "common/threads.h"
|
||||
#include "common/virtualMemory.h"
|
||||
#include "kernel/memory.h"
|
||||
#include "kernel/pthread.h"
|
||||
#include "libs/errno.h"
|
||||
#include "loader/runtimeLinker.h"
|
||||
#include "loader/systemContent.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
@@ -18,11 +24,16 @@ using Libs::LibKernel::Memory::VirtualQueryInfo;
|
||||
|
||||
// Prospero ABI?
|
||||
constexpr uint64_t SceKernelPageSize = 0x4000;
|
||||
constexpr uint64_t SceKernelTotalPhysicalSize = 13824ull * 1024ull * 1024ull;
|
||||
constexpr uint64_t TestFlexibleMemorySize = 3072ull * 1024ull * 1024ull;
|
||||
constexpr int SceKernelProtCpuRead = 0x01;
|
||||
constexpr int SceKernelProtCpuRw = 0x02;
|
||||
constexpr int SceKernelProtCpuExec = 0x04;
|
||||
constexpr int SceKernelMapFixed = 0x10;
|
||||
constexpr int SceKernelMapNoOverwrite = 0x80;
|
||||
constexpr int SceKernelMapDmemCompat = 0x400;
|
||||
constexpr int SceKernelMapNoCoalesce = 0x400000;
|
||||
constexpr int SceKernelMapAligned64Kb = 16 << 24;
|
||||
constexpr int SceKernelVqFindNext = 1;
|
||||
constexpr int SceKernelMtypeC = 11;
|
||||
constexpr uint64_t SceKernelDirectMemoryStart = 0;
|
||||
@@ -94,6 +105,29 @@ void InitSubsystems() {
|
||||
Config::Load(options);
|
||||
|
||||
slist->Add(log, {core, config});
|
||||
Check("InitSubsystems", slist->InitAll(false), "failed to initialize logging subsystem");
|
||||
|
||||
const auto param_json =
|
||||
std::filesystem::temp_directory_path() /
|
||||
("kyty_virtual_memory_" +
|
||||
std::to_string(reinterpret_cast<uintptr_t>(&initialized)) + ".json");
|
||||
constexpr char json[] = R"({"kernel":{"flexibleMemorySize":3221225472}})";
|
||||
Common::File param_file;
|
||||
Check("InitSubsystems", param_file.Create(param_json), "failed to create temporary param.json");
|
||||
uint32_t bytes_written = 0;
|
||||
param_file.Write(json, sizeof(json) - 1, &bytes_written);
|
||||
param_file.Close();
|
||||
Check("InitSubsystems", bytes_written == sizeof(json) - 1,
|
||||
"failed to write temporary param.json");
|
||||
|
||||
Loader::SystemContentLoadParamSfo(param_json);
|
||||
const auto flexible_memory_size = Loader::SystemContentGetFlexibleMemorySize();
|
||||
Check("InitSubsystems", Common::File::DeleteFile(param_json),
|
||||
"failed to remove temporary param.json");
|
||||
Check("InitSubsystems", flexible_memory_size == TestFlexibleMemorySize,
|
||||
"failed to read flexible memory size from param.json");
|
||||
Libs::LibKernel::Memory::SetFlexibleMemorySize(flexible_memory_size);
|
||||
|
||||
slist->Add(memory, {core, log, thread});
|
||||
Check("InitSubsystems", slist->InitAll(false), "failed to initialize memory subsystem");
|
||||
|
||||
@@ -131,6 +165,13 @@ size_t AvailableFlexibleMemory(const char* test) {
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t ConfiguredFlexibleMemory(const char* test) {
|
||||
size_t size = 0;
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelConfiguredFlexibleMemorySize(&size),
|
||||
"KernelConfiguredFlexibleMemorySize");
|
||||
return size;
|
||||
}
|
||||
|
||||
uint64_t MapNamedFlexible(const char* test, uint64_t size, int prot, const char* name) {
|
||||
void* addr = nullptr;
|
||||
const int ret =
|
||||
@@ -190,6 +231,387 @@ void TestProsperoArgumentAndInfoSizeContracts() {
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestGuestAddressSpaceOwnsReservationsBeforeBacking() {
|
||||
const char* test = "GuestAddressSpaceOwnsReservationsBeforeBacking";
|
||||
void* addr = nullptr;
|
||||
|
||||
Check(test, Libs::LibKernel::Memory::TestGuestBackingOutsideAddressSpace(),
|
||||
"boot-time shared backing alias overlaps an owned guest interval");
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelReserveVirtualRange(&addr, SceKernelPageSize, 0,
|
||||
SceKernelPageSize),
|
||||
"KernelReserveVirtualRange");
|
||||
const auto base = reinterpret_cast<uint64_t>(addr);
|
||||
Check(test, Libs::LibKernel::Memory::TestGuestAddressRangeIsOwned(base, SceKernelPageSize),
|
||||
"guest reservation was allocated outside the early owner");
|
||||
Check(test, Libs::LibKernel::Memory::TestPlaceholderRangeIsFree(base, SceKernelPageSize),
|
||||
"semantic reservation replaced the owner's placeholder");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(
|
||||
base, SceKernelPageSize, Common::VirtualMemory::Mode::NoAccess),
|
||||
"owner rejected a sparse placeholder protection no-op");
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize), "KernelMunmap");
|
||||
Check(test, Libs::LibKernel::Memory::TestPlaceholderRangeIsFree(base, SceKernelPageSize),
|
||||
"released semantic reservation escaped owner control");
|
||||
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestGuestAddressSpaceHasNoFixedFallback() {
|
||||
const char* test = "GuestAddressSpaceHasNoFixedFallback";
|
||||
const auto unowned_address = reinterpret_cast<void*>(0x10000);
|
||||
void* addr = unowned_address;
|
||||
|
||||
CheckFailed(test,
|
||||
Libs::LibKernel::Memory::KernelReserveVirtualRange(
|
||||
&addr, SceKernelPageSize, SceKernelMapFixed | SceKernelMapNoOverwrite,
|
||||
SceKernelPageSize),
|
||||
"KernelReserveVirtualRange(unowned fixed address)");
|
||||
Check(test, reinterpret_cast<uint64_t>(addr) == 0x10000,
|
||||
"failed fixed reservation unexpectedly moved");
|
||||
|
||||
addr = unowned_address;
|
||||
CheckFailed(test,
|
||||
Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory(
|
||||
&addr, SceKernelPageSize, SceKernelProtCpuRw,
|
||||
SceKernelMapFixed | SceKernelMapNoOverwrite, "unowned_flexible"),
|
||||
"KernelMapNamedFlexibleMemory(unowned fixed address)");
|
||||
|
||||
int64_t phys_addr = -1;
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
||||
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), SceKernelPageSize,
|
||||
SceKernelPageSize, SceKernelMtypeC, &phys_addr),
|
||||
"KernelAllocateDirectMemory");
|
||||
addr = unowned_address;
|
||||
CheckFailed(test,
|
||||
Libs::LibKernel::Memory::KernelMapNamedDirectMemory(
|
||||
&addr, SceKernelPageSize, SceKernelProtCpuRw,
|
||||
SceKernelMapFixed | SceKernelMapNoOverwrite, phys_addr, SceKernelPageSize,
|
||||
"unowned_direct"),
|
||||
"KernelMapNamedDirectMemory(unowned fixed address)");
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelCheckedReleaseDirectMemory(phys_addr, SceKernelPageSize),
|
||||
"KernelCheckedReleaseDirectMemory");
|
||||
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestGuestFreeRangeSearchDoesNotUnderflow() {
|
||||
const char* test = "GuestFreeRangeSearchDoesNotUnderflow";
|
||||
|
||||
Check(test, Libs::LibKernel::Memory::TestGuestFreeRangeBounds(),
|
||||
"free-range containment accepted a candidate beyond the range end");
|
||||
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestFlexibleMemoryCapacityIsBootFixed() {
|
||||
const char* test = "FlexibleMemoryCapacityIsBootFixed";
|
||||
const auto configured = ConfiguredFlexibleMemory(test);
|
||||
const auto baseline = AvailableFlexibleMemory(test);
|
||||
const auto backing = Libs::LibKernel::Memory::TestGuestBackingSize();
|
||||
|
||||
Check(test, configured == TestFlexibleMemorySize,
|
||||
"boot flexible pool did not use the param.json value");
|
||||
Check(test, configured == baseline, "boot flexible pool did not start at configured capacity");
|
||||
Check(test, backing == SceKernelTotalPhysicalSize,
|
||||
"boot backing is not the single 13.5 GiB physical file");
|
||||
Check(test, backing == Libs::LibKernel::Memory::KernelGetDirectMemorySize() + configured,
|
||||
"direct and flexible regions do not partition the boot backing");
|
||||
|
||||
const auto address =
|
||||
MapNamedFlexible(test, SceKernelPageSize, SceKernelProtCpuRw, "boot_fixed_flexible");
|
||||
Check(test, ConfiguredFlexibleMemory(test) == configured,
|
||||
"configured flexible capacity changed after allocation");
|
||||
Check(test, Libs::LibKernel::Memory::TestGuestBackingSize() == backing,
|
||||
"shared backing size changed after allocation");
|
||||
Check(test, AvailableFlexibleMemory(test) == baseline - SceKernelPageSize,
|
||||
"flexible allocation did not consume the boot-time pool");
|
||||
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(address, SceKernelPageSize),
|
||||
"KernelMunmap");
|
||||
Check(test, ConfiguredFlexibleMemory(test) == configured,
|
||||
"configured flexible capacity changed after release");
|
||||
Check(test, AvailableFlexibleMemory(test) == baseline,
|
||||
"flexible release did not restore the boot-time pool");
|
||||
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestFlexibleMemoryUsesSharedBacking() {
|
||||
const char* test = "FlexibleMemoryUsesSharedBacking";
|
||||
const auto baseline = AvailableFlexibleMemory(test);
|
||||
void* address = nullptr;
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory(
|
||||
&address, SceKernelPageSize * 2, SceKernelProtCpuRw, 0, "shared_flexible"),
|
||||
"KernelMapNamedFlexibleMemory");
|
||||
const auto base = reinterpret_cast<uint64_t>(address);
|
||||
Check(test, Libs::LibKernel::Memory::TestGuestAddressRangeIsOwned(base, SceKernelPageSize * 2),
|
||||
"flexible mapping escaped the guest owner");
|
||||
|
||||
constexpr uint64_t first_value = 0x464c45584241434bull; // "FLEXBACK"
|
||||
constexpr uint64_t second_value = 0x534841524544464cull; // "SHAREDFL"
|
||||
*reinterpret_cast<uint64_t*>(base) = first_value;
|
||||
uint64_t value = 0;
|
||||
Check(test, Libs::LibKernel::Memory::TryReadBacking(base, &value, sizeof(value)),
|
||||
"TryReadBacking did not resolve flexible memory");
|
||||
Check(test, value == first_value, "backing did not observe a flexible-memory CPU write");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::TryWriteBacking(base + SceKernelPageSize, &second_value,
|
||||
sizeof(second_value)),
|
||||
"TryWriteBacking did not resolve flexible memory");
|
||||
Check(test, *reinterpret_cast<uint64_t*>(base + SceKernelPageSize) == second_value,
|
||||
"flexible-memory view did not observe a backing write");
|
||||
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize * 2),
|
||||
"KernelMunmap");
|
||||
Check(test, AvailableFlexibleMemory(test) == baseline,
|
||||
"flexible backing offsets were not returned to the boot-time pool");
|
||||
Check(test, !Libs::LibKernel::Memory::TryReadBacking(base, &value, sizeof(value)),
|
||||
"unmapped flexible memory remained registered in the backing owner");
|
||||
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestFlexibleDmemCompatAndAlignmentFlags() {
|
||||
const char* test = "FlexibleDmemCompatAndAlignmentFlags";
|
||||
const auto baseline = AvailableFlexibleMemory(test);
|
||||
void* address = nullptr;
|
||||
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory(
|
||||
&address, SceKernelPageSize, SceKernelProtCpuRw,
|
||||
SceKernelMapDmemCompat | SceKernelMapAligned64Kb, "dmem_compat"),
|
||||
"KernelMapNamedFlexibleMemory(DMEM_COMPAT|ALIGNED_64KB)");
|
||||
const auto base = reinterpret_cast<uint64_t>(address);
|
||||
Check(test, (base & (0x10000 - 1u)) == 0, "SDK alignment flag was not honored");
|
||||
const auto info = Query(test, base);
|
||||
Check(test, info.is_flexible == 1 && info.is_stack == 0,
|
||||
"SCE_KERNEL_MAP_DMEM_COMPAT was misclassified as MAP_STACK");
|
||||
Check(test, AvailableFlexibleMemory(test) + SceKernelPageSize == baseline,
|
||||
"DMEM_COMPAT mapping did not consume boot-time flexible backing");
|
||||
|
||||
void* stack_start = reinterpret_cast<void*>(UINT64_MAX);
|
||||
void* stack_end = reinterpret_cast<void*>(UINT64_MAX);
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelIsStack(reinterpret_cast<void*>(base), &stack_start,
|
||||
&stack_end),
|
||||
"KernelIsStack");
|
||||
Check(test, stack_start == nullptr && stack_end == nullptr,
|
||||
"DMEM_COMPAT flexible mapping was reported as a stack");
|
||||
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize),
|
||||
"KernelMunmap");
|
||||
Check(test, AvailableFlexibleMemory(test) == baseline,
|
||||
"DMEM_COMPAT cleanup did not restore flexible capacity");
|
||||
|
||||
void* opaque = nullptr;
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory(
|
||||
&opaque, SceKernelPageSize, SceKernelProtCpuRw, 0x8000, "opaque_runtime_flag"),
|
||||
"KernelMapNamedFlexibleMemory(opaque runtime flag)");
|
||||
Check(test, AvailableFlexibleMemory(test) + SceKernelPageSize == baseline,
|
||||
"opaque runtime flag mapping did not consume boot-time flexible backing");
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast<uint64_t>(opaque),
|
||||
SceKernelPageSize),
|
||||
"KernelMunmap(opaque runtime flag)");
|
||||
Check(test, AvailableFlexibleMemory(test) == baseline,
|
||||
"opaque runtime flag cleanup did not restore flexible capacity");
|
||||
|
||||
void* invalid_flag = nullptr;
|
||||
CheckFailed(
|
||||
test,
|
||||
Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory(
|
||||
&invalid_flag, SceKernelPageSize, SceKernelProtCpuRw, 0x10000, "unsupported_flag"),
|
||||
"KernelMapNamedFlexibleMemory(unsupported flag)");
|
||||
|
||||
void* invalid_alignment = nullptr;
|
||||
CheckFailed(test,
|
||||
Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory(
|
||||
&invalid_alignment, SceKernelPageSize, SceKernelProtCpuRw, 13 << 24,
|
||||
"invalid_alignment"),
|
||||
"KernelMapNamedFlexibleMemory(invalid alignment)");
|
||||
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestFlexibleNoCoalescePreservesBoundaries() {
|
||||
const char* test = "FlexibleNoCoalescePreservesBoundaries";
|
||||
const auto baseline = AvailableFlexibleMemory(test);
|
||||
void* reserve = nullptr;
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelReserveVirtualRange(
|
||||
&reserve, SceKernelPageSize * 2, 0, SceKernelPageSize),
|
||||
"KernelReserveVirtualRange");
|
||||
const auto base = reinterpret_cast<uint64_t>(reserve);
|
||||
|
||||
void* left = reinterpret_cast<void*>(base);
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory(
|
||||
&left, SceKernelPageSize, SceKernelProtCpuRw,
|
||||
SceKernelMapFixed | SceKernelMapNoCoalesce, "no_coalesce"),
|
||||
"KernelMapNamedFlexibleMemory(left)");
|
||||
void* right = reinterpret_cast<void*>(base + SceKernelPageSize);
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory(
|
||||
&right, SceKernelPageSize, SceKernelProtCpuRw,
|
||||
SceKernelMapFixed | SceKernelMapNoCoalesce, "no_coalesce"),
|
||||
"KernelMapNamedFlexibleMemory(right)");
|
||||
|
||||
ExpectRange(test, Query(test, base), base, base + SceKernelPageSize, SceKernelProtCpuRw, 1, 0,
|
||||
0, 1, "no_coalesce");
|
||||
ExpectRange(test, Query(test, base + SceKernelPageSize), base + SceKernelPageSize,
|
||||
base + SceKernelPageSize * 2, SceKernelProtCpuRw, 1, 0, 0, 1, "no_coalesce");
|
||||
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize * 2),
|
||||
"KernelMunmap");
|
||||
Check(test, AvailableFlexibleMemory(test) == baseline,
|
||||
"NO_COALESCE cleanup did not restore flexible capacity");
|
||||
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestFlexibleMemoryReuseIsZeroFilled() {
|
||||
const char* test = "FlexibleMemoryReuseIsZeroFilled";
|
||||
const auto baseline = AvailableFlexibleMemory(test);
|
||||
const auto first =
|
||||
MapNamedFlexible(test, SceKernelPageSize, SceKernelProtCpuRw, "flexible_zero_source");
|
||||
std::memset(reinterpret_cast<void*>(first), 0xa5, SceKernelPageSize);
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(first, SceKernelPageSize),
|
||||
"KernelMunmap(source)");
|
||||
|
||||
const auto reused =
|
||||
MapNamedFlexible(test, SceKernelPageSize, SceKernelProtCpuRw, "flexible_zero_reuse");
|
||||
const auto* bytes = reinterpret_cast<const uint8_t*>(reused);
|
||||
Check(test,
|
||||
std::all_of(bytes, bytes + SceKernelPageSize, [](uint8_t value) { return value == 0; }),
|
||||
"reused flexible backing exposed stale bytes");
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(reused, SceKernelPageSize),
|
||||
"KernelMunmap(reuse)");
|
||||
Check(test, AvailableFlexibleMemory(test) == baseline,
|
||||
"zero-fill test leaked flexible backing capacity");
|
||||
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestGuestStackUsesPrivateOwnerMemoryAndCache() {
|
||||
const char* test = "GuestStackUsesPrivateOwnerMemoryAndCache";
|
||||
const auto baseline = AvailableFlexibleMemory(test);
|
||||
uint64_t first = 0;
|
||||
uint64_t second = 0;
|
||||
uint64_t map_size = 0;
|
||||
|
||||
Check(test, Libs::LibKernel::TestGuestStackOwnerLifecycle(&first, &second, &map_size),
|
||||
"guest stack owner lifecycle failed");
|
||||
Check(test, first != 0 && first == second, "guest stack cache did not reuse its owner mapping");
|
||||
Check(test, map_size != 0 && (map_size & (SceKernelPageSize - 1u)) == 0,
|
||||
"guest stack mapping is not 16 KiB aligned");
|
||||
Check(test, AvailableFlexibleMemory(test) == baseline,
|
||||
"private guest stack changed flexible backing capacity");
|
||||
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestMainEntryUsesGuestStackAndDisablesHostChecks() {
|
||||
const char* test = "MainEntryUsesGuestStackAndDisablesHostChecks";
|
||||
|
||||
Check(test, Loader::TestMainEntryUsesGuestStack(),
|
||||
"main-entry stack switch did not preserve the guest/host stack invariants");
|
||||
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestFragmentedBackingUnmapRollback() {
|
||||
const char* test = "FragmentedBackingUnmapRollback";
|
||||
const auto baseline = AvailableFlexibleMemory(test);
|
||||
const auto left =
|
||||
MapNamedFlexible(test, SceKernelPageSize, SceKernelProtCpuRw, "backing_hole_left");
|
||||
const auto blocker =
|
||||
MapNamedFlexible(test, SceKernelPageSize, SceKernelProtCpuRw, "backing_blocker");
|
||||
const auto right =
|
||||
MapNamedFlexible(test, SceKernelPageSize, SceKernelProtCpuRw, "backing_hole_right");
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(left, SceKernelPageSize),
|
||||
"KernelMunmap(left hole)");
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(right, SceKernelPageSize),
|
||||
"KernelMunmap(right hole)");
|
||||
|
||||
const auto fragmented =
|
||||
MapNamedFlexible(test, SceKernelPageSize * 2, SceKernelProtCpuRw, "fragmented_backing");
|
||||
auto* first_word = reinterpret_cast<uint64_t*>(fragmented);
|
||||
auto* last_word =
|
||||
reinterpret_cast<uint64_t*>(fragmented + SceKernelPageSize * 2 - sizeof(uint64_t));
|
||||
*first_word = 0x465241474c454654ull; // "FRAGLEFT"
|
||||
*last_word = 0x4652414752474854ull; // "FRAGRGHT"
|
||||
|
||||
Libs::LibKernel::Memory::TestFailGuestBackingStoreUnmapAfter(1);
|
||||
CheckFailed(test, Libs::LibKernel::Memory::KernelMunmap(fragmented, SceKernelPageSize * 2),
|
||||
"KernelMunmap(injected second-view failure)");
|
||||
ExpectRange(test, Query(test, fragmented), fragmented, fragmented + SceKernelPageSize * 2,
|
||||
SceKernelProtCpuRw, 1, 0, 0, 1, "fragmented_backing");
|
||||
Check(test, *first_word == 0x465241474c454654ull && *last_word == 0x4652414752474854ull,
|
||||
"transactional backing-unmap rollback lost mapped contents");
|
||||
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(fragmented, SceKernelPageSize * 2),
|
||||
"KernelMunmap(retry)");
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(blocker, SceKernelPageSize),
|
||||
"KernelMunmap(blocker)");
|
||||
Check(test, AvailableFlexibleMemory(test) == baseline,
|
||||
"fragmented backing rollback test leaked flexible capacity");
|
||||
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestRuntimeMemoryOwnerLifecycle() {
|
||||
const char* test = "RuntimeMemoryOwnerLifecycle";
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::AllocateRuntimeMemory(0x10000, SceKernelPageSize,
|
||||
Common::VirtualMemory::Mode::ReadWrite,
|
||||
"runtime_outside_owner", true) == 0,
|
||||
"fixed runtime allocation escaped the guest owner");
|
||||
|
||||
const auto base = Libs::LibKernel::Memory::AllocateRuntimeMemory(
|
||||
0, SceKernelPageSize * 2, Common::VirtualMemory::Mode::ReadWrite, "runtime_lifecycle");
|
||||
Check(test, base != 0, "runtime allocation failed");
|
||||
Check(test, Libs::LibKernel::Memory::TestGuestAddressRangeIsOwned(base, SceKernelPageSize * 2),
|
||||
"runtime allocation is outside the owner");
|
||||
*reinterpret_cast<uint64_t*>(base) = 0x52554e54494d454full; // "RUNTIMEO"
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::ProtectGuestMemory(base, SceKernelPageSize,
|
||||
Common::VirtualMemory::Mode::Read),
|
||||
"runtime protection failed");
|
||||
Check(test, Libs::LibKernel::Memory::FreeGuestMemory(base, SceKernelPageSize * 2),
|
||||
"runtime free failed");
|
||||
Check(test, Libs::LibKernel::Memory::TestPlaceholderRangeIsFree(base, SceKernelPageSize * 2),
|
||||
"runtime free did not restore the owner placeholder");
|
||||
|
||||
const auto reused = Libs::LibKernel::Memory::AllocateRuntimeMemory(
|
||||
base, SceKernelPageSize * 2, Common::VirtualMemory::Mode::ReadWrite, "runtime_reuse", true);
|
||||
Check(test, reused == base, "fixed runtime allocation did not reuse the owner placeholder");
|
||||
Check(test, Libs::LibKernel::Memory::FreeGuestMemory(reused, SceKernelPageSize * 2),
|
||||
"reused runtime free failed");
|
||||
|
||||
const auto adjacent_first = Libs::LibKernel::Memory::AllocateRuntimeMemory(
|
||||
0, SceKernelPageSize, Common::VirtualMemory::Mode::ReadWrite, "runtime_adjacent_first");
|
||||
Check(test, adjacent_first != 0, "first adjacent runtime allocation failed");
|
||||
const auto adjacent_second = Libs::LibKernel::Memory::AllocateRuntimeMemory(
|
||||
adjacent_first + SceKernelPageSize, SceKernelPageSize,
|
||||
Common::VirtualMemory::Mode::ReadWrite, "runtime_adjacent_second", true);
|
||||
Check(test, adjacent_second == adjacent_first + SceKernelPageSize,
|
||||
"second adjacent runtime allocation failed");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::FreeGuestMemory(adjacent_first, SceKernelPageSize * 2),
|
||||
"combined adjacent runtime free failed");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::TestPlaceholderRangeIsFree(adjacent_first,
|
||||
SceKernelPageSize * 2),
|
||||
"combined adjacent runtime free did not restore one owner placeholder");
|
||||
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestFlexibleMapQueryAndWholeMunmap() {
|
||||
const char* test = "FlexibleMapQueryAndWholeMunmap";
|
||||
const auto baseline = AvailableFlexibleMemory(test);
|
||||
@@ -341,9 +763,11 @@ void TestDirectMapQueryOffsetAndPartialMunmap() {
|
||||
&addr, SceKernelPageSize * 4, SceKernelProtCpuRw, 0, phys_addr, SceKernelPageSize,
|
||||
"prospero_direct"),
|
||||
"KernelMapNamedDirectMemory");
|
||||
const auto base = reinterpret_cast<uint64_t>(addr);
|
||||
const auto phys = static_cast<uint64_t>(phys_addr);
|
||||
void* alias = nullptr;
|
||||
const auto base = reinterpret_cast<uint64_t>(addr);
|
||||
const auto phys = static_cast<uint64_t>(phys_addr);
|
||||
Check(test, Libs::LibKernel::Memory::TestGuestAddressRangeIsOwned(base, SceKernelPageSize * 4),
|
||||
"direct mapping escaped the guest owner");
|
||||
void* alias = nullptr;
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelMapNamedDirectMemory(
|
||||
&alias, SceKernelPageSize * 4, SceKernelProtCpuRw, 0, phys_addr, SceKernelPageSize,
|
||||
@@ -430,6 +854,192 @@ void TestDirectMapQueryOffsetAndPartialMunmap() {
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestDirectPartialProtectUnmapPreservesNeighbors() {
|
||||
const char* test = "DirectPartialProtectUnmapPreservesNeighbors";
|
||||
const auto size = SceKernelPageSize * 3;
|
||||
int64_t phys_addr = 0;
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
||||
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), size, SceKernelPageSize,
|
||||
SceKernelMtypeC, &phys_addr),
|
||||
"KernelAllocateDirectMemory");
|
||||
|
||||
void* address = nullptr;
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelMapNamedDirectMemory(&address, size, SceKernelProtCpuRw,
|
||||
0, phys_addr, SceKernelPageSize,
|
||||
"partial_protect_direct"),
|
||||
"KernelMapNamedDirectMemory");
|
||||
const auto base = reinterpret_cast<uint64_t>(address);
|
||||
CheckOk(
|
||||
test,
|
||||
Libs::LibKernel::Memory::KernelMprotect(reinterpret_cast<void*>(base + SceKernelPageSize),
|
||||
SceKernelPageSize, SceKernelProtCpuRead),
|
||||
"KernelMprotect(middle)");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(
|
||||
base, size, Common::VirtualMemory::Mode::Read),
|
||||
"owner could not protect fragmented backing views");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(
|
||||
base, size, Common::VirtualMemory::Mode::ReadWrite),
|
||||
"owner could not restore fragmented backing views");
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelMunmap(base + SceKernelPageSize, SceKernelPageSize),
|
||||
"KernelMunmap(middle)");
|
||||
|
||||
Common::VirtualMemory::Mode old_left {};
|
||||
Common::VirtualMemory::Mode old_right {};
|
||||
Check(test,
|
||||
Common::VirtualMemory::Protect(base, SceKernelPageSize,
|
||||
Common::VirtualMemory::Mode::ReadWrite, &old_left),
|
||||
"could not inspect left-page protection");
|
||||
Check(test,
|
||||
Common::VirtualMemory::Protect(base + SceKernelPageSize * 2, SceKernelPageSize,
|
||||
Common::VirtualMemory::Mode::ReadWrite, &old_right),
|
||||
"could not inspect right-page protection");
|
||||
Check(test, old_left == Common::VirtualMemory::Mode::ReadWrite,
|
||||
"partial unmap changed the left neighbor protection");
|
||||
Check(test, old_right == Common::VirtualMemory::Mode::ReadWrite,
|
||||
"partial unmap changed the right neighbor protection");
|
||||
*reinterpret_cast<uint64_t*>(base) = 0x4c45465450524f54ull; // "LEFTPROT"
|
||||
*reinterpret_cast<uint64_t*>(base + SceKernelPageSize * 2) =
|
||||
0x5247485450524f54ull; // "RGHTPROT"
|
||||
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, size),
|
||||
"KernelReleaseDirectMemory");
|
||||
ExpectUnmapped(test, base);
|
||||
ExpectUnmapped(test, base + SceKernelPageSize * 2);
|
||||
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestDirectMapValidationBeforeOwnerMutation() {
|
||||
const char* test = "DirectMapValidationBeforeOwnerMutation";
|
||||
int64_t invalid = -1;
|
||||
CheckFailed(test,
|
||||
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
||||
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), SceKernelPageSize + 1,
|
||||
SceKernelPageSize, SceKernelMtypeC, &invalid),
|
||||
"KernelAllocateDirectMemory(unaligned size)");
|
||||
Check(test, invalid == -1, "invalid direct allocation changed the output address");
|
||||
CheckFailed(test,
|
||||
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
||||
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), SceKernelPageSize,
|
||||
0x1000, SceKernelMtypeC, &invalid),
|
||||
"KernelAllocateDirectMemory(sub-page alignment)");
|
||||
Check(test, invalid == -1, "invalid alignment changed the output address");
|
||||
|
||||
int64_t phys_addr = 0;
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
||||
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), SceKernelPageSize * 2,
|
||||
SceKernelPageSize, SceKernelMtypeC, &phys_addr),
|
||||
"KernelAllocateDirectMemory");
|
||||
|
||||
auto expect_invalid = [&](size_t len, int prot, int flags, int64_t phys, size_t alignment,
|
||||
const char* action) {
|
||||
void* address = nullptr;
|
||||
CheckFailed(test,
|
||||
Libs::LibKernel::Memory::KernelMapDirectMemory(&address, len, prot, flags, phys,
|
||||
alignment),
|
||||
action);
|
||||
Check(test, address == nullptr, "invalid direct map changed the output address");
|
||||
};
|
||||
expect_invalid(SceKernelPageSize + 1, SceKernelProtCpuRw, 0, phys_addr, SceKernelPageSize,
|
||||
"KernelMapDirectMemory(unaligned size)");
|
||||
expect_invalid(SceKernelPageSize, SceKernelProtCpuRw, 0, phys_addr + 1, SceKernelPageSize,
|
||||
"KernelMapDirectMemory(unaligned physical address)");
|
||||
expect_invalid(SceKernelPageSize, SceKernelProtCpuExec, 0, phys_addr, SceKernelPageSize,
|
||||
"KernelMapDirectMemory(executable)");
|
||||
|
||||
void* aligned = nullptr;
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelMapDirectMemory(
|
||||
&aligned, SceKernelPageSize, SceKernelProtCpuRw, 0, phys_addr, 0xc000),
|
||||
"KernelMapDirectMemory(16K-multiple alignment)");
|
||||
Check(test, reinterpret_cast<uint64_t>(aligned) % 0xc000 == 0,
|
||||
"non-power-of-two 16K alignment was not honored");
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast<uint64_t>(aligned),
|
||||
SceKernelPageSize),
|
||||
"KernelMunmap(16K-multiple alignment)");
|
||||
|
||||
void* ignored_flag = nullptr;
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelMapDirectMemory(&ignored_flag, SceKernelPageSize,
|
||||
SceKernelProtCpuRw, 0x08, phys_addr,
|
||||
SceKernelPageSize),
|
||||
"KernelMapDirectMemory(ignored flag)");
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast<uint64_t>(ignored_flag),
|
||||
SceKernelPageSize),
|
||||
"KernelMunmap(ignored flag)");
|
||||
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, SceKernelPageSize * 2),
|
||||
"KernelReleaseDirectMemory");
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestDirectReleaseRollbackRestoresOwnerMapping() {
|
||||
const char* test = "DirectReleaseRollbackRestoresOwnerMapping";
|
||||
int64_t phys_addr = 0;
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
||||
0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), SceKernelPageSize,
|
||||
SceKernelPageSize, SceKernelMtypeC, &phys_addr),
|
||||
"KernelAllocateDirectMemory");
|
||||
void* address = nullptr;
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelMapNamedDirectMemory(
|
||||
&address, SceKernelPageSize, SceKernelProtCpuRw, 0, phys_addr, SceKernelPageSize,
|
||||
"release_rollback"),
|
||||
"KernelMapNamedDirectMemory");
|
||||
const auto base = reinterpret_cast<uint64_t>(address);
|
||||
*reinterpret_cast<uint64_t*>(base) = 0x52454c524f4c4c42ull; // "RELROLLB"
|
||||
|
||||
Libs::LibKernel::Memory::TestFailNextPhysicalMemoryUnmap();
|
||||
CheckFailed(
|
||||
test,
|
||||
Libs::LibKernel::Memory::KernelCheckedReleaseDirectMemory(phys_addr, SceKernelPageSize),
|
||||
"KernelCheckedReleaseDirectMemory(injected failure)");
|
||||
ExpectRange(test, Query(test, base), base, base + SceKernelPageSize, SceKernelProtCpuRw, 0, 1,
|
||||
0, 1, "release_rollback", static_cast<uint64_t>(phys_addr));
|
||||
Check(test, *reinterpret_cast<uint64_t*>(base) == 0x52454c524f4c4c42ull,
|
||||
"release rollback lost the shared-backing contents");
|
||||
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelCheckedReleaseDirectMemory(phys_addr, SceKernelPageSize),
|
||||
"KernelCheckedReleaseDirectMemory(retry)");
|
||||
ExpectUnmapped(test, base);
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestDirectReleaseContracts() {
|
||||
const char* test = "DirectReleaseContracts";
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(0, 0),
|
||||
"KernelReleaseDirectMemory(zero length)");
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelCheckedReleaseDirectMemory(0, 0),
|
||||
"KernelCheckedReleaseDirectMemory(zero length)");
|
||||
CheckFailed(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(1, SceKernelPageSize),
|
||||
"KernelReleaseDirectMemory(unaligned start)");
|
||||
CheckFailed(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(0, SceKernelPageSize + 1),
|
||||
"KernelReleaseDirectMemory(unaligned size)");
|
||||
|
||||
const auto free_offset = static_cast<int64_t>(
|
||||
Libs::LibKernel::Memory::KernelGetDirectMemorySize() - SceKernelPageSize);
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelReleaseDirectMemory(free_offset, SceKernelPageSize),
|
||||
"KernelReleaseDirectMemory(unallocated range)");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::KernelCheckedReleaseDirectMemory(
|
||||
free_offset, SceKernelPageSize) == Libs::LibKernel::KERNEL_ERROR_ENOENT,
|
||||
"checked release did not report an unallocated range");
|
||||
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestReleasedReserveCanBeReused() {
|
||||
const char* test = "ReleasedReserveCanBeReused";
|
||||
void* addr = nullptr;
|
||||
@@ -478,21 +1088,23 @@ void TestMunmapAcrossAdjacentFlexibleMappings() {
|
||||
"KernelMapNamedFlexibleMemory(right)");
|
||||
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::ClampRangeSize(base + SceKernelPageSize - 0x100, 0x200) ==
|
||||
0x200,
|
||||
Libs::LibKernel::Memory::ClampRangeSize(base + SceKernelPageSize - 0x100, 0x200) == 0x200,
|
||||
"ClampRangeSize did not cross adjacent committed mappings");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(
|
||||
base, SceKernelPageSize * 2, Common::VirtualMemory::Mode::Read),
|
||||
"owner could not protect adjacent backing mappings");
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::ProtectGuestHostMemory(
|
||||
base, SceKernelPageSize * 2, Common::VirtualMemory::Mode::ReadWrite),
|
||||
"owner could not restore adjacent backing mappings");
|
||||
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize * 2),
|
||||
"KernelMunmap(adjacent mappings)");
|
||||
Check(test, AvailableFlexibleMemory(test) == baseline,
|
||||
"multi-range unmap leaked flexible-memory budget");
|
||||
ExpectRange(test, Query(test, base), base, base + SceKernelPageSize, 0, 0, 0, 0, 0,
|
||||
"adjacent_left");
|
||||
ExpectRange(test, Query(test, base + SceKernelPageSize), base + SceKernelPageSize,
|
||||
base + SceKernelPageSize * 2, 0, 0, 0, 0, 0, "adjacent_right");
|
||||
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize * 2),
|
||||
"KernelMunmap(restored reserve)");
|
||||
ExpectUnmapped(test, base);
|
||||
ExpectUnmapped(test, base + SceKernelPageSize);
|
||||
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
@@ -548,6 +1160,52 @@ void TestNonzeroDirectOffsetAliasesSharedBacking() {
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestDirectMapAcrossContiguousAllocations() {
|
||||
const char* test = "DirectMapAcrossContiguousAllocations";
|
||||
const auto end = Libs::LibKernel::Memory::KernelGetDirectMemorySize();
|
||||
int64_t first = 0;
|
||||
int64_t second = 0;
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
||||
0, end, SceKernelPageSize, SceKernelPageSize, SceKernelMtypeC, &first),
|
||||
"KernelAllocateDirectMemory(first)");
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
||||
0, end, SceKernelPageSize, SceKernelPageSize, SceKernelMtypeC, &second),
|
||||
"KernelAllocateDirectMemory(second)");
|
||||
Check(test, second == first + static_cast<int64_t>(SceKernelPageSize),
|
||||
"test allocations are not physically contiguous");
|
||||
|
||||
void* mapping = nullptr;
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelMapNamedDirectMemory(
|
||||
&mapping, SceKernelPageSize * 2, SceKernelProtCpuRw, 0, first, SceKernelPageSize,
|
||||
"contiguous_allocations"),
|
||||
"KernelMapNamedDirectMemory");
|
||||
auto* words = reinterpret_cast<uint64_t*>(mapping);
|
||||
words[0] = 0x434f4e5449474c46ull; // "CONTIGLF"
|
||||
*reinterpret_cast<uint64_t*>(reinterpret_cast<uint64_t>(mapping) + SceKernelPageSize) =
|
||||
0x434f4e5449475254ull; // "CONTIGRT"
|
||||
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelCheckedReleaseDirectMemory(first, SceKernelPageSize * 2),
|
||||
"KernelCheckedReleaseDirectMemory(contiguous span)");
|
||||
ExpectUnmapped(test, reinterpret_cast<uint64_t>(mapping));
|
||||
|
||||
int64_t reclaimed = -1;
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelAllocateDirectMemory(
|
||||
0, end, SceKernelPageSize * 2, SceKernelPageSize, SceKernelMtypeC, &reclaimed),
|
||||
"KernelAllocateDirectMemory(reclaimed)");
|
||||
Check(test, reclaimed == first, "released contiguous span was not coalesced");
|
||||
CheckOk(
|
||||
test,
|
||||
Libs::LibKernel::Memory::KernelCheckedReleaseDirectMemory(reclaimed, SceKernelPageSize * 2),
|
||||
"KernelCheckedReleaseDirectMemory(reclaimed)");
|
||||
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestDirectPhysicalFreeRangeReuseAndCoalescing() {
|
||||
const char* test = "DirectPhysicalFreeRangeReuseAndCoalescing";
|
||||
const auto end = Libs::LibKernel::Memory::KernelGetDirectMemorySize();
|
||||
@@ -933,38 +1591,6 @@ void TestFixedReserveRollbackConsumesRestoredPlaceholder() {
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestFixedReserveRollbackRestoresDecommittedHostPages() {
|
||||
const char* test = "FixedReserveRollbackRestoresDecommittedHostPages";
|
||||
constexpr uint64_t size = SceKernelPageSize * 3;
|
||||
void* mapped = nullptr;
|
||||
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory(&mapped, size, SceKernelProtCpuRw,
|
||||
0, "host_reserve_rollback"),
|
||||
"KernelMapNamedFlexibleMemory");
|
||||
const auto base = reinterpret_cast<uint64_t>(mapped);
|
||||
*reinterpret_cast<uint64_t*>(base) = 0x4b595459484f5354ull; // "KYTYHOST"
|
||||
*reinterpret_cast<uint64_t*>(base + SceKernelPageSize * 2) =
|
||||
0x4b5954595441494cull; // "KYTYTAIL"
|
||||
|
||||
Libs::LibKernel::Memory::TestFailHostReservationAfter(1);
|
||||
void* replacement = mapped;
|
||||
CheckFailed(
|
||||
test,
|
||||
Libs::LibKernel::Memory::KernelReserveVirtualRange(
|
||||
&replacement, size, SceKernelMapFixed | SceKernelMapNoCoalesce, SceKernelPageSize),
|
||||
"KernelReserveVirtualRange(partial host reservation)");
|
||||
Check(test, *reinterpret_cast<uint64_t*>(base) == 0x4b595459484f5354ull,
|
||||
"rollback did not restore the first flexible page");
|
||||
Check(test, *reinterpret_cast<uint64_t*>(base + SceKernelPageSize * 2) == 0x4b5954595441494cull,
|
||||
"rollback damaged the flexible tail page");
|
||||
ExpectRange(test, Query(test, base), base, base + size, SceKernelProtCpuRw, 1, 0, 0, 1,
|
||||
"host_reserve_rollback");
|
||||
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, size), "KernelMunmap");
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestFixedReserveRangeAddRollbackKeepsPlaceholder() {
|
||||
const char* test = "FixedReserveRangeAddRollbackKeepsPlaceholder";
|
||||
constexpr uint64_t size = SceKernelPageSize * 4;
|
||||
@@ -1055,8 +1681,10 @@ void TestLargeHintedReserveHostsSmallDirectMap() {
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys, SceKernelPageSize * 2),
|
||||
"KernelReleaseDirectMemory");
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast<uint64_t>(window), window_size),
|
||||
"KernelMunmap(window reserve)");
|
||||
Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast<uint64_t>(window) +
|
||||
SceKernelPageSize * 2,
|
||||
window_size - SceKernelPageSize * 2),
|
||||
"KernelMunmap(window reserve remainder)");
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast<uint64_t>(arena), arena_size),
|
||||
"KernelMunmap(arena reserve)");
|
||||
@@ -1153,18 +1781,25 @@ void TestProsperoSampleMemoryPoolExpandCommit() {
|
||||
SceKernelProtCpuRw, 0),
|
||||
"KernelMemoryPoolCommit");
|
||||
ExpectRange(test, Query(test, base), base, base + commit_len, SceKernelProtCpuRw, 0, 0, 1, 1);
|
||||
Check(test, Libs::LibKernel::Memory::TestGuestAddressRangeIsOwned(base, commit_len),
|
||||
"pooled commit escaped the guest owner");
|
||||
Check(test, AvailableFlexibleMemory(test) == flexible_baseline,
|
||||
"pooled commit consumed flexible memory instead of expanded direct "
|
||||
"backing");
|
||||
CheckFailed(test,
|
||||
Libs::LibKernel::Memory::KernelReleaseDirectMemory(pool_offset,
|
||||
SceKernelMemoryPoolExpandLen),
|
||||
"KernelReleaseDirectMemory(committed pool expansion)");
|
||||
Libs::LibKernel::Memory::KernelCheckedReleaseDirectMemory(
|
||||
pool_offset, SceKernelMemoryPoolExpandLen),
|
||||
"KernelCheckedReleaseDirectMemory(committed pool expansion)");
|
||||
|
||||
constexpr uint64_t first_value = 0x504f4f4c4241434bull; // "POOLBACK"
|
||||
constexpr uint64_t second_value = 0x5348415245444d45ull; // "SHAREDME"
|
||||
*reinterpret_cast<uint64_t*>(base) = first_value;
|
||||
*reinterpret_cast<uint64_t*>(base + SceKernelMemoryPoolCommitLen) = second_value;
|
||||
uint64_t backing_read = 0;
|
||||
Check(test, Libs::LibKernel::Memory::TryReadBacking(base, &backing_read, sizeof(backing_read)),
|
||||
"TryReadBacking did not resolve pooled memory");
|
||||
Check(test, backing_read == first_value,
|
||||
"shared backing did not observe a pooled-memory CPU write");
|
||||
|
||||
CheckOk(
|
||||
test,
|
||||
@@ -1276,8 +1911,10 @@ void TestFragmentedMemoryPoolBacking() {
|
||||
"KernelMemoryPoolCommit(fragmented recommit)");
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, commit_len),
|
||||
"KernelMunmap(fragmented commit)");
|
||||
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelMemoryPoolReserveLen),
|
||||
"KernelMunmap(fragmented reserve cleanup)");
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelMunmap(base + commit_len,
|
||||
SceKernelMemoryPoolReserveLen - commit_len),
|
||||
"KernelMunmap(fragmented reserve remainder)");
|
||||
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelReleaseDirectMemory(first_pool,
|
||||
@@ -1427,22 +2064,32 @@ void TestMemoryPoolCommitDecommitQueryFlags() {
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestProgramMemoryRegistrationAndProtection() {
|
||||
const char* test = "ProgramMemoryRegistrationAndProtection";
|
||||
void TestProgramMemoryAllocationAndProtection() {
|
||||
const char* test = "ProgramMemoryAllocationAndProtection";
|
||||
const auto size = SceKernelPageSize * 3;
|
||||
const auto base = Common::VirtualMemory::Alloc(0, size, Common::VirtualMemory::Mode::ReadWrite);
|
||||
Check(test, base != 0, "program host allocation failed");
|
||||
|
||||
Libs::LibKernel::Memory::RegisterProgramMemory(
|
||||
base, size, Common::VirtualMemory::Mode::ReadWrite, "program_test");
|
||||
const auto base = Libs::LibKernel::Memory::AllocateProgramMemory(
|
||||
0x900000000, size, Common::VirtualMemory::Mode::ReadWrite, "program_test");
|
||||
Check(test, base != 0, "program guest allocation failed");
|
||||
Check(test, Libs::LibKernel::Memory::TestGuestAddressRangeIsOwned(base, size),
|
||||
"program allocation escaped the guest owner");
|
||||
ExpectRange(test, Query(test, base), base, base + size,
|
||||
SceKernelProtCpuRead | SceKernelProtCpuRw, 0, 0, 0, 1, "program_test");
|
||||
|
||||
Libs::LibKernel::Memory::UpdateProgramMemoryProtection(base, SceKernelPageSize,
|
||||
Common::VirtualMemory::Mode::Read);
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::ProtectGuestMemory(base, SceKernelPageSize,
|
||||
Common::VirtualMemory::Mode::Read),
|
||||
"ProtectGuestMemory(first page) failed");
|
||||
ExpectRange(test, Query(test, base), base, base + SceKernelPageSize, SceKernelProtCpuRead, 0, 0,
|
||||
0, 1, "program_test");
|
||||
|
||||
Common::VirtualMemory::Mode previous_mode = Common::VirtualMemory::Mode::NoAccess;
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::ProtectGuestMemory(
|
||||
base, SceKernelPageSize, Common::VirtualMemory::Mode::ReadWrite, &previous_mode),
|
||||
"ProtectGuestMemory(tracked restore) failed");
|
||||
Check(test, previous_mode == Common::VirtualMemory::Mode::Read,
|
||||
"semantic guest protection did not preserve its tracked old mode");
|
||||
|
||||
CheckOk(test,
|
||||
Libs::LibKernel::Memory::KernelMprotect(
|
||||
reinterpret_cast<void*>(base + SceKernelPageSize - 0x10), 0x20,
|
||||
@@ -1451,24 +2098,44 @@ void TestProgramMemoryRegistrationAndProtection() {
|
||||
ExpectRange(test, Query(test, base), base, base + size,
|
||||
SceKernelProtCpuRead | SceKernelProtCpuRw, 0, 0, 0, 1, "program_test");
|
||||
|
||||
Libs::LibKernel::Memory::UpdateProgramMemoryProtection(
|
||||
base + SceKernelPageSize * 2, SceKernelPageSize, Common::VirtualMemory::Mode::Read);
|
||||
Check(test,
|
||||
Libs::LibKernel::Memory::ProtectGuestMemory(
|
||||
base + SceKernelPageSize * 2, SceKernelPageSize, Common::VirtualMemory::Mode::Read),
|
||||
"ProtectGuestMemory(last page) failed");
|
||||
ExpectRange(test, Query(test, base + SceKernelPageSize * 2), base + SceKernelPageSize * 2,
|
||||
base + size, SceKernelProtCpuRead, 0, 0, 0, 1, "program_test");
|
||||
|
||||
Libs::LibKernel::Memory::UnregisterProgramMemory(base, size);
|
||||
Check(test, Libs::LibKernel::Memory::FreeGuestMemory(base, size), "program guest free failed");
|
||||
ExpectUnmapped(test, base);
|
||||
Check(test, Common::VirtualMemory::Free(base), "program host free failed");
|
||||
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
void TestModuleRelocationUsesWritableHostMapping() {
|
||||
const char* test = "ModuleRelocationUsesWritableHostMapping";
|
||||
Check(test, Loader::TestModuleRelocationUsesWritableHostMapping(),
|
||||
"module relocation did not retain writable host memory and semantic guest protection");
|
||||
std::printf("[host] %-48s ok\n", test);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
InitSubsystems();
|
||||
|
||||
RunTest(TestProsperoArgumentAndInfoSizeContracts);
|
||||
RunTest(TestGuestAddressSpaceOwnsReservationsBeforeBacking);
|
||||
RunTest(TestGuestAddressSpaceHasNoFixedFallback);
|
||||
RunTest(TestGuestFreeRangeSearchDoesNotUnderflow);
|
||||
RunTest(TestFlexibleMemoryCapacityIsBootFixed);
|
||||
RunTest(TestFlexibleMemoryUsesSharedBacking);
|
||||
RunTest(TestFlexibleDmemCompatAndAlignmentFlags);
|
||||
RunTest(TestFlexibleNoCoalescePreservesBoundaries);
|
||||
RunTest(TestFlexibleMemoryReuseIsZeroFilled);
|
||||
RunTest(TestGuestStackUsesPrivateOwnerMemoryAndCache);
|
||||
RunTest(TestMainEntryUsesGuestStackAndDisablesHostChecks);
|
||||
RunTest(TestFragmentedBackingUnmapRollback);
|
||||
RunTest(TestRuntimeMemoryOwnerLifecycle);
|
||||
RunTest(TestFlexibleMapQueryAndWholeMunmap);
|
||||
RunTest(TestPartialFlexibleMunmapAndFindNext);
|
||||
RunTest(TestReserveMapFixedAndNoOverwrite);
|
||||
@@ -1476,7 +2143,12 @@ int main() {
|
||||
RunTest(TestReleasedReserveCanBeReused);
|
||||
RunTest(TestMunmapAcrossAdjacentFlexibleMappings);
|
||||
RunTest(TestDirectMapQueryOffsetAndPartialMunmap);
|
||||
RunTest(TestDirectPartialProtectUnmapPreservesNeighbors);
|
||||
RunTest(TestDirectMapValidationBeforeOwnerMutation);
|
||||
RunTest(TestDirectReleaseRollbackRestoresOwnerMapping);
|
||||
RunTest(TestDirectReleaseContracts);
|
||||
RunTest(TestNonzeroDirectOffsetAliasesSharedBacking);
|
||||
RunTest(TestDirectMapAcrossContiguousAllocations);
|
||||
RunTest(TestDirectPhysicalFreeRangeReuseAndCoalescing);
|
||||
RunTest(TestDirectAlignmentStaysWithinSearchRange);
|
||||
RunTest(TestDefaultDirectMapUsesSystemAddressRange);
|
||||
@@ -1485,7 +2157,6 @@ int main() {
|
||||
RunTest(TestFixedReserveReplacesPartialDirectMapping);
|
||||
RunTest(TestFixedReserveRollbackConsumesRestoredPlaceholder);
|
||||
RunTest(TestFixedReserveRollbackSkipsUntouchedChunks);
|
||||
RunTest(TestFixedReserveRollbackRestoresDecommittedHostPages);
|
||||
RunTest(TestFixedReserveRangeAddRollbackKeepsPlaceholder);
|
||||
RunTest(TestLargeHintedReserveHostsSmallDirectMap);
|
||||
RunTest(TestMemoryPoolAlignmentContracts);
|
||||
@@ -1493,7 +2164,8 @@ int main() {
|
||||
RunTest(TestFragmentedMemoryPoolBacking);
|
||||
RunTest(TestMemoryPoolMultiRangeDecommit);
|
||||
RunTest(TestMemoryPoolCommitDecommitQueryFlags);
|
||||
RunTest(TestProgramMemoryRegistrationAndProtection);
|
||||
RunTest(TestProgramMemoryAllocationAndProtection);
|
||||
RunTest(TestModuleRelocationUsesWritableHostMapping);
|
||||
|
||||
if (g_failed_tests != 0) {
|
||||
std::printf("VirtualMemoryAllocationTests: %d case(s) failed\n", g_failed_tests);
|
||||
|
||||
Reference in New Issue
Block a user