memory: replace live direct mappings without exposing an inaccessible gap

This commit is contained in:
nmzik committed 2026-10-04 18:39:34 +04:00
1 parent 1f6be3d570
commit 46900d8042
4 files changed
+279 -53

No files matched your search

+93 -15
View File
@@ -166,7 +166,8 @@ static bool VirtualRangesOverlap(uint64_t left_start, uint64_t left_size, uint64
}
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
static uint32_t g_test_backing_store_unmaps_before_failure = UINT32_MAX;
static uint32_t g_test_backing_store_unmaps_before_failure = UINT32_MAX;
static callback_func_t g_test_before_backing_map = nullptr;
#endif
#include "memoryAddressSpace.inc"
@@ -3024,6 +3025,22 @@ int KYTY_SYSV_ABI KernelMapDirectMemory(void** addr, size_t len, int prot, int f
return g_guest_address_space->MapBacking(target_addr, len, direct_memory_start, mode,
&shared_failure);
};
std::vector<PhysicalMemory::AllocatedBlock> replaced_mappings;
auto restore_mappings = [&](size_t count, bool restore_views) {
for (size_t index = 0; index < count; ++index) {
const auto& old = replaced_mappings[index];
if (restore_views) {
EXIT_IF(!g_guest_address_space->MapBacking(old.map_vaddr, old.map_size,
old.start_addr, old.mode));
}
EXIT_IF(!g_physical_memory->Map(old.map_vaddr, old.start_addr, old.map_size,
old.prot, old.mode, old.gpu_mode));
g_physical_memory->SetVirtualRangeName(old.map_vaddr, old.map_size, old.name);
g_physical_memory->SetVirtualRangeMemoryType(old.map_vaddr, old.map_size,
old.memory_type);
}
MapGpuRange(in_addr, len);
};
if (fixed) {
if (in_addr == 0 || (in_addr & (PAGE_SIZE - 1u)) != 0 ||
(alignment != 0 && in_addr % alignment != 0)) {
@@ -3034,22 +3051,68 @@ int KYTY_SYSV_ABI KernelMapDirectMemory(void** addr, size_t len, int prot, int f
}
std::vector<VirtualRanges::Range> reserved_ranges;
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
if (g_virtual_ranges->QuerySpan(in_addr, len, &reserved_ranges) &&
std::all_of(reserved_ranges.begin(), reserved_ranges.end(), [](const auto& range) {
return range.type == VirtualRangeType::Reserved;
return range.type == VirtualRangeType::Direct;
})) {
uint64_t covered = 0;
{
Common::LockGuard lock(g_physical_memory->GetMutex());
for (const auto& mapping: g_physical_memory->GetMappings()) {
const auto start = std::max(in_addr, mapping.map_vaddr);
const auto end = std::min(in_addr + len, mapping.map_vaddr + mapping.map_size);
if (start >= end) {
continue;
}
auto old = mapping;
old.start_addr += start - old.map_vaddr;
old.map_vaddr = start;
old.size = old.map_size = end - start;
covered += old.map_size;
replaced_mappings.push_back(old);
}
}
if (covered != len) {
return KERNEL_ERROR_EBUSY;
}
UnmapGpuRange(in_addr, len);
reserved_target = true;
}
if (!reserved_target && ReplaceFixedRangeWithReserved(in_addr, len)) {
reserved_target = true;
}
if (!reserved_target) {
return KERNEL_ERROR_ENOMEM;
}
if (map_shared_fixed(in_addr)) {
for (size_t index = 0; index < replaced_mappings.size(); ++index) {
const auto& old = replaced_mappings[index];
GpuAccessMode old_gpu_mode = GpuAccessMode::NoAccess;
if (!g_physical_memory->Unmap(old.map_vaddr, old.map_size, &old_gpu_mode)) {
restore_mappings(index, false);
return KERNEL_ERROR_EBUSY;
}
}
// Keep the old view accessible until mmap atomically replaces it. In particular,
// do not publish a temporary reserved/PROT_NONE range between two direct views.
if (!map_shared_fixed(in_addr)) {
restore_mappings(replaced_mappings.size(), true);
return KERNEL_ERROR_ENOMEM;
}
out_addr = in_addr;
shared_backing = true;
} else
#endif
{
if (g_virtual_ranges->QuerySpan(in_addr, len, &reserved_ranges) &&
std::all_of(reserved_ranges.begin(), reserved_ranges.end(), [](const auto& range) {
return range.type == VirtualRangeType::Reserved;
})) {
UnmapGpuRange(in_addr, len);
reserved_target = true;
}
if (!reserved_target && ReplaceFixedRangeWithReserved(in_addr, len)) {
reserved_target = true;
}
if (!reserved_target) {
return KERNEL_ERROR_ENOMEM;
}
if (map_shared_fixed(in_addr)) {
out_addr = in_addr;
shared_backing = true;
}
}
} else {
constexpr size_t DEFAULT_ALIGNMENT = 0x4000;
@@ -3105,15 +3168,22 @@ int KYTY_SYSV_ABI KernelMapDirectMemory(void** addr, size_t len, int prot, int f
if (!g_physical_memory->Map(out_addr, direct_memory_start, len, prot, mode, gpu_mode)) {
LOGF_COLOR(Log::Color::Red, "\t [Fail]\n");
EXIT_IF(!g_guest_address_space->UnmapBacking(out_addr, len));
if (replaced_mappings.empty()) {
EXIT_IF(!g_guest_address_space->UnmapBacking(out_addr, len));
} else {
restore_mappings(replaced_mappings.size(), true);
}
return KERNEL_ERROR_EBUSY;
}
PhysicalMemory::AllocatedBlock mapped_block {};
g_physical_memory->Find(direct_memory_start, false, &mapped_block);
const bool published = reserved_target
const bool published = !replaced_mappings.empty() || reserved_target
? g_virtual_ranges->ReplaceSpan(
out_addr, len, VirtualRangeType::Reserved, direct_memory_start,
out_addr, len, replaced_mappings.empty()
? VirtualRangeType::Reserved
: VirtualRangeType::Direct,
direct_memory_start,
prot, mapped_block.memory_type, VirtualRangeType::Direct, "")
: g_virtual_ranges->Add(out_addr, len, direct_memory_start, prot,
mapped_block.memory_type,
@@ -3121,7 +3191,11 @@ int KYTY_SYSV_ABI KernelMapDirectMemory(void** addr, size_t len, int prot, int f
if (!published) {
GpuAccessMode rollback_gpu_mode = GpuAccessMode::NoAccess;
EXIT_IF(!g_physical_memory->Unmap(out_addr, len, &rollback_gpu_mode));
EXIT_IF(!g_guest_address_space->UnmapBacking(out_addr, len));
if (replaced_mappings.empty()) {
EXIT_IF(!g_guest_address_space->UnmapBacking(out_addr, len));
} else {
restore_mappings(replaced_mappings.size(), true);
}
return KERNEL_ERROR_EBUSY;
}
@@ -3504,6 +3578,10 @@ int KYTY_SYSV_ABI KernelReserveVirtualRange(void** addr, size_t len, int flags,
}
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
void TestBeforeNextBackingMap(callback_func_t callback) {
g_test_before_backing_map = callback;
}
void TestFailNextPhysicalMemoryUnmap() {
TestFailPhysicalMemoryUnmapAfter(0);
}