mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-22 03:46:53 +02:00
947576363e
When CreateFileMapping cannot reserve the guest backing store the log only carries the raw Win32 error, which gives no clue that the paging file is the thing to change. Print the size that was asked for and say so directly when the failure is ERROR_NO_SYSTEM_RESOURCES or ERROR_COMMITMENT_LIMIT.
1358 lines
44 KiB
C++
1358 lines
44 KiB
C++
// Internal memory address-space helpers included by memory.cpp.
|
|
// Kept in the same translation unit because they depend on memory.cpp-local helpers.
|
|
|
|
static constexpr uint64_t HOST_SYSTEM_MANAGED_MIN = 0x0000040000ull;
|
|
static constexpr uint64_t HOST_SYSTEM_MANAGED_MAX = 0x07ffffbfffull;
|
|
static constexpr uint64_t HOST_SYSTEM_RESERVED_MIN = HOST_SYSTEM_MANAGED_MAX + 1u;
|
|
#if defined(__APPLE__) && (defined(__x86_64__) || defined(_M_X64))
|
|
static constexpr uint64_t HOST_SYSTEM_RESERVED_MAX = 0x0fbfffffffull;
|
|
static constexpr uint64_t HOST_USER_MIN = 0x7000000000ull;
|
|
#else
|
|
static constexpr uint64_t HOST_SYSTEM_RESERVED_MAX = 0x0fffffffffull;
|
|
static constexpr uint64_t HOST_USER_MIN = 0x1000000000ull;
|
|
#endif
|
|
static constexpr uint64_t HOST_USER_MAX = 0xfbffffffffull;
|
|
|
|
static bool GuestFreeRangeContains(uint64_t base, uint64_t range_size, uint64_t candidate,
|
|
uint64_t size) {
|
|
if (UINT64_MAX - base < range_size || candidate < base) {
|
|
return false;
|
|
}
|
|
const auto range_end = base + range_size;
|
|
return candidate <= range_end && size <= range_end - candidate;
|
|
}
|
|
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
static bool CoalescePlaceholders(uint64_t vaddr, uint64_t size) {
|
|
return size != 0 && VirtualFree(reinterpret_cast<void*>(vaddr), size,
|
|
MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS) != 0;
|
|
}
|
|
#endif
|
|
|
|
class GuestBackingStore {
|
|
public:
|
|
enum class FailureReason {
|
|
None,
|
|
BackingUnavailable,
|
|
PhysicalRangeOutOfBounds,
|
|
FixedMapViewFailed,
|
|
PlaceholderMapFailed,
|
|
ProtectFailed,
|
|
WrongHostAddress,
|
|
AddressRangeUnavailable,
|
|
};
|
|
|
|
explicit GuestBackingStore(uint64_t size): m_size(size) {
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
m_handle = CreateFileMappingA(
|
|
INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE | SEC_COMMIT,
|
|
static_cast<DWORD>(m_size >> 32u), static_cast<DWORD>(m_size & 0xffffffffu), nullptr);
|
|
if (m_handle == nullptr) {
|
|
LOGF_COLOR(Log::Color::Red,
|
|
"\t direct-memory backing: CreateFileMapping failed: 0x%08" PRIx32 "\n",
|
|
static_cast<uint32_t>(GetLastError()));
|
|
return;
|
|
}
|
|
m_backing_base = static_cast<uint8_t*>(
|
|
MapViewOfFile(m_handle, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, m_size));
|
|
if (m_backing_base == nullptr) {
|
|
LOGF_COLOR(Log::Color::Red,
|
|
"\t direct-memory backing: writable alias failed: 0x%08" PRIx32 "\n",
|
|
static_cast<uint32_t>(GetLastError()));
|
|
CloseHandle(m_handle);
|
|
m_handle = nullptr;
|
|
}
|
|
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
|
#if defined(__APPLE__)
|
|
{
|
|
char shm_name[64];
|
|
snprintf(shm_name, sizeof(shm_name), "/kyty-dm-%d-%llx", static_cast<int>(getpid()),
|
|
static_cast<unsigned long long>(reinterpret_cast<uintptr_t>(this)));
|
|
m_fd = shm_open(shm_name, O_RDWR | O_CREAT | O_EXCL, 0600);
|
|
if (m_fd >= 0) {
|
|
shm_unlink(shm_name);
|
|
}
|
|
}
|
|
#else
|
|
m_fd = static_cast<int>(syscall(SYS_memfd_create, "KytyDirectMemory", 0));
|
|
#endif
|
|
if (m_fd < 0) {
|
|
LOGF_COLOR(Log::Color::Red, "\t direct-memory backing: shared fd creation failed: %d\n",
|
|
errno);
|
|
return;
|
|
}
|
|
if (ftruncate(m_fd, static_cast<off_t>(m_size)) != 0) {
|
|
LOGF_COLOR(Log::Color::Red, "\t direct-memory backing: ftruncate failed: %d\n", errno);
|
|
close(m_fd);
|
|
m_fd = -1;
|
|
return;
|
|
}
|
|
auto* backing = mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0);
|
|
if (backing == MAP_FAILED) {
|
|
LOGF_COLOR(Log::Color::Red, "\t direct-memory backing: writable alias failed: %d\n",
|
|
errno);
|
|
close(m_fd);
|
|
m_fd = -1;
|
|
return;
|
|
}
|
|
m_backing_base = static_cast<uint8_t*>(backing);
|
|
#endif
|
|
}
|
|
|
|
~GuestBackingStore() {
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
for (const auto& [_, entry]: m_maps) {
|
|
UnmapViewPreservePlaceholder(reinterpret_cast<void*>(entry.vaddr), entry.size);
|
|
}
|
|
m_maps.clear();
|
|
if (m_backing_base != nullptr) {
|
|
UnmapViewOfFile(m_backing_base);
|
|
}
|
|
if (m_handle != nullptr) {
|
|
CloseHandle(m_handle);
|
|
}
|
|
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
|
for (const auto& [_, entry]: m_maps) {
|
|
EXIT_IF(
|
|
!UnmapViewPreservePlaceholder(reinterpret_cast<void*>(entry.vaddr), entry.size));
|
|
}
|
|
m_maps.clear();
|
|
if (m_backing_base != nullptr) {
|
|
munmap(m_backing_base, m_size);
|
|
}
|
|
if (m_fd >= 0) {
|
|
close(m_fd);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
KYTY_CLASS_NO_COPY(GuestBackingStore);
|
|
|
|
static const char* GetFailureReasonName(FailureReason reason) {
|
|
switch (reason) {
|
|
case FailureReason::None: return "n/a";
|
|
case FailureReason::BackingUnavailable: return "backing-unavailable";
|
|
case FailureReason::PhysicalRangeOutOfBounds: return "physical-range-out-of-bounds";
|
|
case FailureReason::FixedMapViewFailed: return "fixed-map-view-failed";
|
|
case FailureReason::PlaceholderMapFailed: return "placeholder-map-failed";
|
|
case FailureReason::ProtectFailed: return "protect-failed";
|
|
case FailureReason::WrongHostAddress: return "wrong-host-address";
|
|
case FailureReason::AddressRangeUnavailable: return "address-range-unavailable";
|
|
default: return "unknown";
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] bool IsAvailable() const {
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
return m_handle != nullptr && m_backing_base != nullptr;
|
|
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
|
return m_fd >= 0 && m_backing_base != nullptr;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
[[nodiscard]] uint64_t GetBackingBase() const {
|
|
return reinterpret_cast<uint64_t>(m_backing_base);
|
|
}
|
|
|
|
[[nodiscard]] uint64_t GetBackingSize() const { return m_size; }
|
|
|
|
bool ZeroRange(uint64_t backing_offset, uint64_t size) {
|
|
if (!IsAvailable() || !RangeOk(backing_offset, size)) {
|
|
return false;
|
|
}
|
|
std::memset(m_backing_base + backing_offset, 0, size);
|
|
return true;
|
|
}
|
|
|
|
bool TryWriteBacking(uint64_t vaddr, const void* data, uint64_t size) {
|
|
if (data == nullptr) {
|
|
return false;
|
|
}
|
|
const auto* source = static_cast<const uint8_t*>(data);
|
|
return TryTransferBacking(vaddr, size,
|
|
[source](uint8_t* backing, uint64_t data_offset, size_t bytes) {
|
|
std::memcpy(backing, source + data_offset, bytes);
|
|
});
|
|
}
|
|
|
|
bool TryReadBacking(uint64_t vaddr, void* data, uint64_t size) {
|
|
if (data == nullptr) {
|
|
return false;
|
|
}
|
|
auto* destination = static_cast<uint8_t*>(data);
|
|
return TryTransferBacking(
|
|
vaddr, size, [destination](uint8_t* backing, uint64_t data_offset, size_t bytes) {
|
|
std::memcpy(destination + data_offset, backing, bytes);
|
|
});
|
|
}
|
|
|
|
bool TryReadSparseBacking(uint64_t vaddr, void* data, uint64_t size) {
|
|
if (!IsAvailable() || data == nullptr || size == 0 || UINT64_MAX - vaddr < size) {
|
|
return false;
|
|
}
|
|
|
|
std::lock_guard lock(m_mutex);
|
|
const auto end = vaddr + size;
|
|
auto first = m_maps.lower_bound(vaddr);
|
|
if (first != m_maps.begin()) {
|
|
auto previous = std::prev(first);
|
|
if (previous->second.vaddr + previous->second.size > vaddr) {
|
|
first = previous;
|
|
}
|
|
}
|
|
for (auto mapping = first; mapping != m_maps.end() && mapping->second.vaddr < end;
|
|
++mapping) {
|
|
const auto& entry = mapping->second;
|
|
const auto start = std::max(vaddr, entry.vaddr);
|
|
const auto bytes = std::min(end, entry.vaddr + entry.size) - start;
|
|
if (!RangeOk(entry.backing_offset + start - entry.vaddr, bytes)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
auto* destination = static_cast<uint8_t*>(data);
|
|
std::memset(destination, 0, size);
|
|
for (auto mapping = first; mapping != m_maps.end() && mapping->second.vaddr < end;
|
|
++mapping) {
|
|
const auto& entry = mapping->second;
|
|
const auto start = std::max(vaddr, entry.vaddr);
|
|
const auto bytes = std::min(end, entry.vaddr + entry.size) - start;
|
|
const auto offset = entry.backing_offset + start - entry.vaddr;
|
|
std::memcpy(destination + start - vaddr, m_backing_base + offset, bytes);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool MapExistingPlaceholderFixed(uint64_t vaddr, uint64_t size, uint64_t backing_offset,
|
|
VirtualMemory::Mode mode,
|
|
FailureReason* failure_reason = nullptr) {
|
|
if (!IsAvailable() || !RangeOk(backing_offset, size)) {
|
|
SetFailure(failure_reason, !IsAvailable() ? FailureReason::BackingUnavailable
|
|
: FailureReason::PhysicalRangeOutOfBounds);
|
|
return false;
|
|
}
|
|
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
void* mapped = ReplacePlaceholderView(vaddr, size, backing_offset, mode, failure_reason);
|
|
if (mapped == nullptr || reinterpret_cast<uint64_t>(mapped) != vaddr) {
|
|
if (mapped != nullptr) {
|
|
UnmapViewPreservePlaceholder(mapped, size);
|
|
SetFailure(failure_reason, FailureReason::WrongHostAddress);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
{
|
|
std::lock_guard lock(m_mutex);
|
|
EXIT_IF(!m_maps.emplace(vaddr, MapEntry {vaddr, size, backing_offset, mode}).second);
|
|
}
|
|
SetFailure(failure_reason, FailureReason::None);
|
|
return true;
|
|
#else
|
|
void* mapped = MapView(vaddr, size, backing_offset, mode, failure_reason);
|
|
if (mapped == nullptr || reinterpret_cast<uint64_t>(mapped) != vaddr) {
|
|
return false;
|
|
}
|
|
{
|
|
std::lock_guard lock(m_mutex);
|
|
EXIT_IF(!m_maps.emplace(vaddr, MapEntry {vaddr, size, backing_offset, mode}).second);
|
|
}
|
|
SetFailure(failure_reason, FailureReason::None);
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool Unmap(uint64_t vaddr, uint64_t size, bool* placeholder_preserved = nullptr) {
|
|
if (size == 0 || UINT64_MAX - vaddr < size) {
|
|
return false;
|
|
}
|
|
|
|
if (placeholder_preserved != nullptr) {
|
|
*placeholder_preserved = false;
|
|
}
|
|
|
|
const auto end = vaddr + size;
|
|
std::vector<MapEntry> targets;
|
|
{
|
|
std::lock_guard lock(m_mutex);
|
|
auto current = vaddr;
|
|
while (current < end) {
|
|
MapEntry entry {};
|
|
if (!FindContainingUnlocked(current, 1, &entry)) {
|
|
return false;
|
|
}
|
|
const auto part_size = std::min(end, entry.vaddr + entry.size) - current;
|
|
targets.push_back(
|
|
{current, part_size, entry.backing_offset + current - entry.vaddr, entry.mode});
|
|
current += part_size;
|
|
}
|
|
}
|
|
|
|
std::vector<MapEntry> removed;
|
|
removed.reserve(targets.size());
|
|
for (const auto& entry: targets) {
|
|
bool part_placeholder_preserved = false;
|
|
if (!UnmapOne(entry.vaddr, entry.size, &part_placeholder_preserved)) {
|
|
for (auto it = removed.rbegin(); it != removed.rend(); ++it) {
|
|
EXIT_IF(!MapExistingPlaceholderFixed(it->vaddr, it->size, it->backing_offset,
|
|
it->mode));
|
|
}
|
|
if (placeholder_preserved != nullptr) {
|
|
*placeholder_preserved = false;
|
|
}
|
|
return false;
|
|
}
|
|
EXIT_IF(!part_placeholder_preserved);
|
|
removed.push_back(entry);
|
|
}
|
|
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
// Each unmapped file view becomes a separate Windows placeholder. The address-space owner
|
|
// records this contiguous guest span as one free range, so normalize fragmented backing
|
|
// views into one placeholder before publishing it there.
|
|
if (removed.size() > 1 && !CoalescePlaceholders(vaddr, size)) {
|
|
for (auto it = removed.rbegin(); it != removed.rend(); ++it) {
|
|
EXIT_IF(!MapExistingPlaceholderFixed(it->vaddr, it->size, it->backing_offset,
|
|
it->mode));
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
if (placeholder_preserved != nullptr) {
|
|
*placeholder_preserved = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool UnmapOne(uint64_t vaddr, uint64_t size, bool* placeholder_preserved = nullptr) {
|
|
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
|
if (g_test_backing_store_unmaps_before_failure == 0) {
|
|
g_test_backing_store_unmaps_before_failure = UINT32_MAX;
|
|
return false;
|
|
}
|
|
if (g_test_backing_store_unmaps_before_failure != UINT32_MAX) {
|
|
g_test_backing_store_unmaps_before_failure--;
|
|
}
|
|
#endif
|
|
|
|
MapEntry old {};
|
|
{
|
|
std::lock_guard lock(m_mutex);
|
|
auto next = m_maps.upper_bound(vaddr);
|
|
if (next != m_maps.begin()) {
|
|
auto entry = std::prev(next);
|
|
if (vaddr + size <= entry->second.vaddr + entry->second.size) {
|
|
old = entry->second;
|
|
m_maps.erase(entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (old.size == 0) {
|
|
return false;
|
|
}
|
|
|
|
const bool exact_unmap = (vaddr == old.vaddr && size == old.size);
|
|
if (exact_unmap) {
|
|
const bool unmapped =
|
|
UnmapViewPreservePlaceholder(reinterpret_cast<void*>(old.vaddr), old.size);
|
|
if (!unmapped) {
|
|
std::lock_guard lock(m_mutex);
|
|
m_maps.emplace(old.vaddr, old);
|
|
return false;
|
|
}
|
|
if (placeholder_preserved != nullptr) {
|
|
*placeholder_preserved = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const uint64_t left_size = vaddr - old.vaddr;
|
|
const uint64_t right_addr = vaddr + size;
|
|
const uint64_t old_end = old.vaddr + old.size;
|
|
const uint64_t right_size = old_end - right_addr;
|
|
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
if (UnmapViewPreservePlaceholder(reinterpret_cast<void*>(old.vaddr), old.size)) {
|
|
bool ok = true;
|
|
if (left_size != 0 && left_size != old.size) {
|
|
ok = SplitPlaceholder(old.vaddr, left_size) && ok;
|
|
}
|
|
if (right_size != 0) {
|
|
ok = SplitPlaceholder(vaddr, size) && ok;
|
|
}
|
|
if (left_size != 0) {
|
|
ok = MapExistingPlaceholderFixed(old.vaddr, left_size, old.backing_offset,
|
|
old.mode) &&
|
|
ok;
|
|
}
|
|
if (right_size != 0) {
|
|
ok = MapExistingPlaceholderFixed(right_addr, right_size,
|
|
old.backing_offset + (right_addr - old.vaddr),
|
|
old.mode) &&
|
|
ok;
|
|
}
|
|
if (ok) {
|
|
if (placeholder_preserved != nullptr) {
|
|
*placeholder_preserved = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (left_size != 0 && Contains(old.vaddr, left_size)) {
|
|
EXIT_IF(!UnmapOne(old.vaddr, left_size));
|
|
}
|
|
if (right_size != 0 && Contains(right_addr, right_size)) {
|
|
EXIT_IF(!UnmapOne(right_addr, right_size));
|
|
}
|
|
EXIT_IF(VirtualFree(reinterpret_cast<void*>(old.vaddr), old.size,
|
|
MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS) == 0);
|
|
EXIT_IF(
|
|
!MapExistingPlaceholderFixed(old.vaddr, old.size, old.backing_offset, old.mode));
|
|
return false;
|
|
}
|
|
std::lock_guard lock(m_mutex);
|
|
m_maps.emplace(old.vaddr, old);
|
|
return false;
|
|
#else
|
|
if (!UnmapViewPreservePlaceholder(reinterpret_cast<void*>(old.vaddr), old.size)) {
|
|
std::lock_guard lock(m_mutex);
|
|
m_maps.emplace(old.vaddr, old);
|
|
return false;
|
|
}
|
|
|
|
bool ok = true;
|
|
if (left_size != 0) {
|
|
ok = MapExistingPlaceholderFixed(old.vaddr, left_size, old.backing_offset, old.mode) &&
|
|
ok;
|
|
}
|
|
if (right_size != 0) {
|
|
ok = MapExistingPlaceholderFixed(right_addr, right_size,
|
|
old.backing_offset + (right_addr - old.vaddr),
|
|
old.mode) &&
|
|
ok;
|
|
}
|
|
if (!ok) {
|
|
if (left_size != 0 && Contains(old.vaddr, left_size)) {
|
|
EXIT_IF(!UnmapOne(old.vaddr, left_size));
|
|
}
|
|
if (right_size != 0 && Contains(right_addr, right_size)) {
|
|
EXIT_IF(!UnmapOne(right_addr, right_size));
|
|
}
|
|
EXIT_IF(
|
|
!MapExistingPlaceholderFixed(old.vaddr, old.size, old.backing_offset, old.mode));
|
|
return false;
|
|
}
|
|
|
|
if (placeholder_preserved != nullptr) {
|
|
*placeholder_preserved = true;
|
|
}
|
|
return ok;
|
|
#endif
|
|
}
|
|
|
|
bool Contains(uint64_t vaddr, uint64_t size) {
|
|
if (size == 0 || UINT64_MAX - vaddr < size) {
|
|
return false;
|
|
}
|
|
|
|
std::lock_guard lock(m_mutex);
|
|
const auto end = vaddr + size;
|
|
auto current = vaddr;
|
|
|
|
while (current < end) {
|
|
MapEntry entry {};
|
|
if (!FindContainingUnlocked(current, 1, &entry)) {
|
|
return false;
|
|
}
|
|
const auto entry_end = entry.vaddr + entry.size;
|
|
if (entry_end <= current) {
|
|
return false;
|
|
}
|
|
current = std::min(end, entry_end);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
template <typename Transfer>
|
|
bool TryTransferBacking(uint64_t vaddr, uint64_t size, Transfer transfer) {
|
|
if (!IsAvailable() || size == 0 || UINT64_MAX - vaddr < size) {
|
|
return false;
|
|
}
|
|
|
|
std::lock_guard lock(m_mutex);
|
|
const auto end = vaddr + size;
|
|
auto current = vaddr;
|
|
while (current < end) {
|
|
MapEntry entry {};
|
|
if (!FindContainingUnlocked(current, 1, &entry)) {
|
|
return false;
|
|
}
|
|
const auto bytes = std::min(end, entry.vaddr + entry.size) - current;
|
|
if (!RangeOk(entry.backing_offset + current - entry.vaddr, bytes)) {
|
|
return false;
|
|
}
|
|
current += bytes;
|
|
}
|
|
|
|
current = vaddr;
|
|
while (current < end) {
|
|
MapEntry entry {};
|
|
EXIT_IF(!FindContainingUnlocked(current, 1, &entry));
|
|
const auto bytes = std::min(end, entry.vaddr + entry.size) - current;
|
|
const auto offset = entry.backing_offset + current - entry.vaddr;
|
|
transfer(m_backing_base + offset, current - vaddr, static_cast<size_t>(bytes));
|
|
current += bytes;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
struct MapEntry {
|
|
uint64_t vaddr = 0;
|
|
uint64_t size = 0;
|
|
uint64_t backing_offset = 0;
|
|
VirtualMemory::Mode mode = VirtualMemory::Mode::NoAccess;
|
|
};
|
|
|
|
bool FindContainingUnlocked(uint64_t vaddr, uint64_t size, MapEntry* out) const {
|
|
EXIT_IF(out == nullptr);
|
|
|
|
if (size == 0 || UINT64_MAX - vaddr < size) {
|
|
return false;
|
|
}
|
|
|
|
auto next = m_maps.upper_bound(vaddr);
|
|
if (next == m_maps.begin()) {
|
|
return false;
|
|
}
|
|
const auto& entry = std::prev(next)->second;
|
|
if (vaddr + size <= entry.vaddr + entry.size) {
|
|
*out = entry;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
[[nodiscard]] bool RangeOk(uint64_t backing_offset, uint64_t size) const {
|
|
return size != 0 && backing_offset < m_size && size <= m_size - backing_offset;
|
|
}
|
|
|
|
static void SetFailure(FailureReason* out, FailureReason reason) {
|
|
if (out != nullptr) {
|
|
*out = reason;
|
|
}
|
|
}
|
|
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
static DWORD GetProtect(VirtualMemory::Mode mode) {
|
|
switch (mode) {
|
|
case VirtualMemory::Mode::Read: return PAGE_READONLY;
|
|
case VirtualMemory::Mode::Write:
|
|
case VirtualMemory::Mode::ReadWrite: return PAGE_READWRITE;
|
|
case VirtualMemory::Mode::Execute: return PAGE_EXECUTE;
|
|
case VirtualMemory::Mode::ExecuteRead: return PAGE_EXECUTE_READ;
|
|
case VirtualMemory::Mode::ExecuteWrite:
|
|
case VirtualMemory::Mode::ExecuteReadWrite: return PAGE_EXECUTE_READWRITE;
|
|
case VirtualMemory::Mode::NoAccess:
|
|
default: return PAGE_NOACCESS;
|
|
}
|
|
}
|
|
|
|
static bool ApplyProtection(void* ptr, uint64_t size, VirtualMemory::Mode mode) {
|
|
DWORD old_protect = 0;
|
|
return VirtualProtect(ptr, size, GetProtect(mode), &old_protect) != 0;
|
|
}
|
|
|
|
static bool SplitPlaceholder(uint64_t vaddr, uint64_t size) {
|
|
if (size == 0) {
|
|
return true;
|
|
}
|
|
return VirtualFree(reinterpret_cast<void*>(vaddr), size,
|
|
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER) != 0;
|
|
}
|
|
|
|
void* ReplacePlaceholderView(uint64_t vaddr, uint64_t size, uint64_t backing_offset,
|
|
VirtualMemory::Mode mode, FailureReason* failure_reason) {
|
|
const DWORD map_protect =
|
|
mode == VirtualMemory::Mode::NoAccess ? PAGE_READWRITE : GetProtect(mode);
|
|
void* ptr =
|
|
MapViewOfFile3(m_handle, GetCurrentProcess(), reinterpret_cast<void*>(vaddr),
|
|
backing_offset, size, MEM_REPLACE_PLACEHOLDER, map_protect, nullptr, 0);
|
|
if (ptr == nullptr || reinterpret_cast<uint64_t>(ptr) != vaddr) {
|
|
if (ptr != nullptr) {
|
|
UnmapViewOfFile2(GetCurrentProcess(), ptr, MEM_PRESERVE_PLACEHOLDER);
|
|
}
|
|
SetFailure(failure_reason, FailureReason::PlaceholderMapFailed);
|
|
return nullptr;
|
|
}
|
|
|
|
if (!ApplyProtection(ptr, size, mode)) {
|
|
UnmapViewOfFile2(GetCurrentProcess(), ptr, MEM_PRESERVE_PLACEHOLDER);
|
|
SetFailure(failure_reason, FailureReason::ProtectFailed);
|
|
return nullptr;
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
static bool UnmapViewPreservePlaceholder(void* vaddr, uint64_t /*size*/) {
|
|
return UnmapViewOfFile2(GetCurrentProcess(), vaddr, MEM_PRESERVE_PLACEHOLDER) != 0;
|
|
}
|
|
|
|
HANDLE m_handle = nullptr;
|
|
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
|
[[nodiscard]] static uint64_t GetMappableTestSize() { return 0x4000; }
|
|
|
|
static int GetProtect(VirtualMemory::Mode mode) {
|
|
switch (mode) {
|
|
case VirtualMemory::Mode::Read: return PROT_READ;
|
|
case VirtualMemory::Mode::Write:
|
|
case VirtualMemory::Mode::ReadWrite: return PROT_READ | PROT_WRITE;
|
|
case VirtualMemory::Mode::Execute: return PROT_EXEC;
|
|
case VirtualMemory::Mode::ExecuteRead: return PROT_EXEC | PROT_READ;
|
|
case VirtualMemory::Mode::ExecuteWrite:
|
|
case VirtualMemory::Mode::ExecuteReadWrite: return PROT_EXEC | PROT_READ | PROT_WRITE;
|
|
case VirtualMemory::Mode::NoAccess:
|
|
default: return PROT_NONE;
|
|
}
|
|
}
|
|
|
|
void* MapView(uint64_t vaddr, uint64_t size, uint64_t backing_offset, VirtualMemory::Mode mode,
|
|
FailureReason* failure_reason) {
|
|
void* ptr = mmap(reinterpret_cast<void*>(vaddr), size, GetProtect(mode),
|
|
MAP_SHARED | MAP_FIXED, m_fd, static_cast<off_t>(backing_offset));
|
|
if (ptr == MAP_FAILED) {
|
|
SetFailure(failure_reason, FailureReason::FixedMapViewFailed);
|
|
return nullptr;
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
// Replace the file view atomically instead of exposing an unmapped hole in the
|
|
// process-lifetime guest reservation.
|
|
static bool UnmapViewPreservePlaceholder(void* vaddr, uint64_t size) {
|
|
void* ptr = mmap(vaddr, size, PROT_NONE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
|
|
return ptr != MAP_FAILED && ptr == vaddr;
|
|
}
|
|
|
|
int m_fd = -1;
|
|
#endif
|
|
|
|
uint8_t* m_backing_base = nullptr;
|
|
uint64_t m_size = 0;
|
|
std::mutex m_mutex;
|
|
std::map<uint64_t, MapEntry> m_maps;
|
|
};
|
|
|
|
class GuestAddressSpace {
|
|
public:
|
|
explicit GuestAddressSpace(uint64_t backing_size) {
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
SYSTEM_INFO info {};
|
|
GetSystemInfo(&info);
|
|
m_granularity = info.dwAllocationGranularity;
|
|
#endif
|
|
ReserveGuestRegions();
|
|
m_backing = new GuestBackingStore(backing_size);
|
|
if (!m_backing->IsAvailable()) {
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
EXIT("could not reserve %" PRIu64 " MB for guest direct memory. Windows commits\n"
|
|
"this up front, so the paging file is usually what needs to be larger.\n",
|
|
backing_size / (1024U * 1024U));
|
|
#else
|
|
EXIT("could not reserve %" PRIu64 " MB for guest direct memory\n",
|
|
backing_size / (1024U * 1024U));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
~GuestAddressSpace() {
|
|
delete m_backing;
|
|
m_backing = nullptr;
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
for (const auto& [start, size]: m_owned) {
|
|
const auto end = start + size;
|
|
auto current = start;
|
|
while (current < end) {
|
|
MEMORY_BASIC_INFORMATION info {};
|
|
EXIT_IF(VirtualQuery(reinterpret_cast<const void*>(current), &info, sizeof(info)) ==
|
|
0);
|
|
const auto region_start = reinterpret_cast<uint64_t>(info.BaseAddress);
|
|
const auto region_end = region_start + static_cast<uint64_t>(info.RegionSize);
|
|
if (info.State == MEM_COMMIT) {
|
|
EXIT_IF(VirtualFree(reinterpret_cast<void*>(region_start), info.RegionSize,
|
|
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER) == 0);
|
|
continue;
|
|
}
|
|
if (info.State == MEM_RESERVE) {
|
|
EXIT_IF(VirtualFree(info.AllocationBase, 0, MEM_RELEASE) == 0);
|
|
}
|
|
current = region_end;
|
|
}
|
|
}
|
|
#else
|
|
for (const auto& [start, size]: m_owned) {
|
|
munmap(reinterpret_cast<void*>(start), size);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
KYTY_CLASS_NO_COPY(GuestAddressSpace);
|
|
|
|
[[nodiscard]] uint64_t GetGranularity() const { return m_granularity; }
|
|
[[nodiscard]] uint64_t GetBackingBase() const { return m_backing->GetBackingBase(); }
|
|
[[nodiscard]] uint64_t GetBackingSize() const { return m_backing->GetBackingSize(); }
|
|
|
|
bool ZeroBacking(uint64_t backing_offset, uint64_t size) {
|
|
return m_backing->ZeroRange(backing_offset, size);
|
|
}
|
|
|
|
[[nodiscard]] bool Owns(uint64_t vaddr, uint64_t size) {
|
|
if (size == 0 || UINT64_MAX - vaddr < size) {
|
|
return false;
|
|
}
|
|
std::lock_guard lock(m_mutex);
|
|
return std::any_of(m_owned.begin(), m_owned.end(), [vaddr, size](const auto& range) {
|
|
return vaddr >= range.first && vaddr + size <= range.first + range.second;
|
|
});
|
|
}
|
|
|
|
[[nodiscard]] bool OverlapsOwned(uint64_t vaddr, uint64_t size) {
|
|
if (size == 0 || UINT64_MAX - vaddr < size) {
|
|
return false;
|
|
}
|
|
std::lock_guard lock(m_mutex);
|
|
return std::any_of(m_owned.begin(), m_owned.end(), [vaddr, size](const auto& range) {
|
|
return VirtualRangesOverlap(vaddr, size, range.first, range.second);
|
|
});
|
|
}
|
|
|
|
bool TryWriteBacking(uint64_t vaddr, const void* data, uint64_t size) {
|
|
return m_backing->TryWriteBacking(vaddr, data, size);
|
|
}
|
|
|
|
bool TryReadBacking(uint64_t vaddr, void* data, uint64_t size) {
|
|
return m_backing->TryReadBacking(vaddr, data, size);
|
|
}
|
|
|
|
bool TryReadSparseBacking(uint64_t vaddr, void* data, uint64_t size) {
|
|
return m_backing->TryReadSparseBacking(vaddr, data, size);
|
|
}
|
|
|
|
bool BackingContains(uint64_t vaddr, uint64_t size) { return m_backing->Contains(vaddr, size); }
|
|
|
|
bool Protect(uint64_t vaddr, uint64_t size, VirtualMemory::Mode mode) {
|
|
if (size == 0 || UINT64_MAX - vaddr < size) {
|
|
return false;
|
|
}
|
|
std::lock_guard lock(m_mutex);
|
|
return ProtectMappedUnlocked(vaddr, size, mode);
|
|
}
|
|
|
|
bool ProtectTransient(uint64_t vaddr, uint64_t size, VirtualMemory::Mode mode) {
|
|
if (size == 0 || UINT64_MAX - vaddr < size) {
|
|
return false;
|
|
}
|
|
|
|
std::lock_guard lock(m_mutex);
|
|
return ProtectMappedUnlocked(vaddr, size, mode);
|
|
}
|
|
|
|
bool MapBacking(uint64_t vaddr, uint64_t size, uint64_t backing_offset,
|
|
VirtualMemory::Mode mode,
|
|
GuestBackingStore::FailureReason* failure_reason = nullptr) {
|
|
std::lock_guard lock(m_mutex);
|
|
if (!ConsumeUnlocked(vaddr, size)) {
|
|
if (failure_reason != nullptr) {
|
|
*failure_reason = GuestBackingStore::FailureReason::AddressRangeUnavailable;
|
|
}
|
|
return false;
|
|
}
|
|
const auto host_mode = VirtualMemory::IsExecute(mode)
|
|
? VirtualMemory::Mode::ExecuteReadWrite
|
|
: VirtualMemory::Mode::ReadWrite;
|
|
if (m_backing->MapExistingPlaceholderFixed(vaddr, size, backing_offset, host_mode,
|
|
failure_reason)) {
|
|
EXIT_IF(!AddMappedUnlocked(vaddr, size, MappingType::Backing));
|
|
return true;
|
|
}
|
|
AddFreeUnlocked(vaddr, size);
|
|
return false;
|
|
}
|
|
|
|
bool UnmapBacking(uint64_t vaddr, uint64_t size) {
|
|
std::lock_guard lock(m_mutex);
|
|
if (!ContainsMappedUnlocked(vaddr, size, MappingType::Backing)) {
|
|
return false;
|
|
}
|
|
bool placeholder_preserved = false;
|
|
if (!m_backing->Unmap(vaddr, size, &placeholder_preserved)) {
|
|
return false;
|
|
}
|
|
if (!placeholder_preserved) {
|
|
return false;
|
|
}
|
|
EXIT_IF(!RemoveMappedUnlocked(vaddr, size, MappingType::Backing));
|
|
AddFreeUnlocked(vaddr, size);
|
|
return true;
|
|
}
|
|
|
|
uint64_t ReserveAligned(uint64_t search_addr, uint64_t size, uint64_t alignment) {
|
|
if (size == 0) {
|
|
return 0;
|
|
}
|
|
alignment = std::max<uint64_t>(alignment != 0 ? alignment : PageSize(), PageSize());
|
|
std::lock_guard lock(m_mutex);
|
|
if (search_addr == 0) {
|
|
auto addr = FindFreeAlignedUnlocked(HOST_SYSTEM_MANAGED_MIN,
|
|
HOST_SYSTEM_MANAGED_MAX + 1u, size, alignment);
|
|
return addr != 0 ? addr
|
|
: FindFreeAlignedUnlocked(HOST_USER_MIN, HOST_USER_MAX + 1u, size,
|
|
alignment);
|
|
}
|
|
return FindFreeAlignedUnlocked(search_addr, HOST_USER_MAX + 1u, size, alignment);
|
|
}
|
|
|
|
uint64_t FindFreeAligned(uint64_t search_start, uint64_t search_end, uint64_t size,
|
|
uint64_t alignment) {
|
|
if (size == 0 || alignment == 0) {
|
|
return 0;
|
|
}
|
|
alignment = std::max<uint64_t>(alignment, PageSize());
|
|
std::lock_guard lock(m_mutex);
|
|
return FindFreeAlignedUnlocked(search_start, search_end, size, alignment);
|
|
}
|
|
|
|
bool ReserveFixed(uint64_t vaddr, uint64_t size) {
|
|
if (vaddr == 0 || size == 0 || !IsPageAligned(vaddr) || !IsPageAligned(size)) {
|
|
return false;
|
|
}
|
|
std::lock_guard lock(m_mutex);
|
|
return FindContaining(vaddr, size) != m_free.end();
|
|
}
|
|
|
|
#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS)
|
|
bool TestContainsFree(uint64_t vaddr, uint64_t size) {
|
|
std::lock_guard lock(m_mutex);
|
|
return FindContaining(vaddr, size) != m_free.end();
|
|
}
|
|
#endif
|
|
|
|
bool ReleaseFree(uint64_t vaddr, uint64_t size) {
|
|
std::lock_guard lock(m_mutex);
|
|
return FindContaining(vaddr, size) != m_free.end();
|
|
}
|
|
|
|
bool Commit(uint64_t vaddr, uint64_t size, VirtualMemory::Mode mode) {
|
|
std::lock_guard lock(m_mutex);
|
|
if (!ConsumeUnlocked(vaddr, size)) {
|
|
return false;
|
|
}
|
|
const auto host_mode = VirtualMemory::IsExecute(mode)
|
|
? VirtualMemory::Mode::ExecuteReadWrite
|
|
: VirtualMemory::Mode::ReadWrite;
|
|
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
auto* ptr = VirtualAlloc2(GetCurrentProcess(), reinterpret_cast<void*>(vaddr), size,
|
|
MEM_RESERVE | MEM_COMMIT | MEM_REPLACE_PLACEHOLDER,
|
|
GetProtect(host_mode), nullptr, 0);
|
|
if (ptr == nullptr || reinterpret_cast<uint64_t>(ptr) != vaddr) {
|
|
if (ptr != nullptr) {
|
|
VirtualFree(ptr, 0, MEM_RELEASE);
|
|
}
|
|
AddFreeUnlocked(vaddr, size);
|
|
return false;
|
|
}
|
|
EXIT_IF(!AddMappedUnlocked(vaddr, size, MappingType::Private));
|
|
return true;
|
|
#else
|
|
if (VirtualMemory::Protect(vaddr, size, host_mode)) {
|
|
EXIT_IF(!AddMappedUnlocked(vaddr, size, MappingType::Private));
|
|
return true;
|
|
}
|
|
AddFreeUnlocked(vaddr, size);
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool ReleaseCommitted(uint64_t vaddr, uint64_t size) {
|
|
if (vaddr == 0 || size == 0 || !IsPageAligned(vaddr) || !IsPageAligned(size)) {
|
|
return false;
|
|
}
|
|
std::lock_guard lock(m_mutex);
|
|
if (!ContainsMappedUnlocked(vaddr, size, MappingType::Private)) {
|
|
return false;
|
|
}
|
|
const auto end = vaddr + size;
|
|
auto current = vaddr;
|
|
std::vector<std::pair<uint64_t, uint64_t>> regions;
|
|
while (current < end) {
|
|
const auto it = FindMappedContainingUnlocked(current);
|
|
const auto part_end = std::min(end, it->second.vaddr + it->second.size);
|
|
regions.emplace_back(current, part_end - current);
|
|
current = part_end;
|
|
}
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
for (const auto& [region_vaddr, region_size]: regions) {
|
|
EXIT_IF(VirtualFree(reinterpret_cast<void*>(region_vaddr), region_size,
|
|
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER) == 0);
|
|
}
|
|
// ReleaseCommitted also publishes the aggregate span as one logical free range.
|
|
EXIT_IF(regions.size() > 1 && !CoalescePlaceholders(vaddr, size));
|
|
#else
|
|
for (const auto& [region_vaddr, region_size]: regions) {
|
|
RestorePlaceholder(region_vaddr, region_size);
|
|
}
|
|
#endif
|
|
EXIT_IF(!RemoveMappedUnlocked(vaddr, size, MappingType::Private));
|
|
AddFreeUnlocked(vaddr, size);
|
|
return true;
|
|
}
|
|
|
|
bool SelfTest() {
|
|
const auto addr = ReserveAligned(0, PageSize(), PageSize());
|
|
if (addr == 0) {
|
|
LOGF_COLOR(Log::Color::Red,
|
|
"\t guest address-space self-test: 16 KiB range unavailable\n");
|
|
return false;
|
|
}
|
|
if (!Commit(addr, PageSize(), VirtualMemory::Mode::ReadWrite)) {
|
|
LOGF_COLOR(Log::Color::Red, "\t guest address-space self-test: commit failed\n");
|
|
return false;
|
|
}
|
|
|
|
auto* ptr = reinterpret_cast<uint64_t*>(addr);
|
|
*ptr = 0x4b59545950484f4cull; // "KYTYPHOL"
|
|
|
|
if (!ReleaseCommitted(addr, PageSize())) {
|
|
LOGF_COLOR(Log::Color::Red,
|
|
"\t guest address-space self-test: release-committed failed\n");
|
|
return false;
|
|
}
|
|
|
|
LOGF_COLOR(Log::Color::Green, "\t guest address-space self-test: ok\n");
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
enum class MappingType { Backing, Private };
|
|
|
|
struct MappedRegion {
|
|
uint64_t vaddr = 0;
|
|
uint64_t size = 0;
|
|
MappingType type = MappingType::Private;
|
|
};
|
|
|
|
static constexpr uint64_t PageSize() { return 0x4000; }
|
|
|
|
static uint64_t AlignUp(uint64_t value, uint64_t alignment) {
|
|
if (alignment == 0) {
|
|
return 0;
|
|
}
|
|
const auto remainder = value % alignment;
|
|
const auto increment = (remainder != 0 ? alignment - remainder : 0);
|
|
return increment <= UINT64_MAX - value ? value + increment : 0;
|
|
}
|
|
|
|
static uint64_t AlignDown(uint64_t value, uint64_t alignment) {
|
|
return value & ~(alignment - 1u);
|
|
}
|
|
|
|
[[nodiscard]] static bool IsPageAligned(uint64_t value) {
|
|
return (value & (PageSize() - 1u)) == 0;
|
|
}
|
|
|
|
[[nodiscard]] bool IsGranularityAligned(uint64_t value) const {
|
|
return (value & (m_granularity - 1u)) == 0;
|
|
}
|
|
|
|
bool ProtectMappedUnlocked(uint64_t vaddr, uint64_t size, VirtualMemory::Mode mode) {
|
|
const auto end = vaddr + size;
|
|
auto it = m_mapped.upper_bound(vaddr);
|
|
if (it != m_mapped.begin()) {
|
|
--it;
|
|
}
|
|
for (; it != m_mapped.end() && it->second.vaddr < end; ++it) {
|
|
const auto& region = it->second;
|
|
const auto range_start = std::max(vaddr, region.vaddr);
|
|
const auto range_end = std::min(end, region.vaddr + region.size);
|
|
if (range_start < range_end &&
|
|
!VirtualMemory::Protect(range_start, range_end - range_start, mode)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ConsumeUnlocked(uint64_t vaddr, uint64_t size) {
|
|
auto it = FindContaining(vaddr, size);
|
|
if (it == m_free.end() || !SplitUnlocked(it, vaddr, size)) {
|
|
return false;
|
|
}
|
|
it = m_free.find(vaddr);
|
|
if (it == m_free.end() || it->second != size) {
|
|
return false;
|
|
}
|
|
m_free.erase(it);
|
|
return true;
|
|
}
|
|
|
|
std::map<uint64_t, MappedRegion>::iterator FindMappedContainingUnlocked(uint64_t vaddr) {
|
|
auto next = m_mapped.upper_bound(vaddr);
|
|
if (next == m_mapped.begin()) {
|
|
return m_mapped.end();
|
|
}
|
|
auto it = std::prev(next);
|
|
return vaddr < it->second.vaddr + it->second.size ? it : m_mapped.end();
|
|
}
|
|
|
|
bool ContainsMappedUnlocked(uint64_t vaddr, uint64_t size, MappingType type) {
|
|
if (size == 0 || UINT64_MAX - vaddr < size) {
|
|
return false;
|
|
}
|
|
const auto end = vaddr + size;
|
|
auto current = vaddr;
|
|
while (current < end) {
|
|
const auto it = FindMappedContainingUnlocked(current);
|
|
if (it == m_mapped.end() || it->second.type != type) {
|
|
return false;
|
|
}
|
|
current = std::min(end, it->second.vaddr + it->second.size);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool AddMappedUnlocked(uint64_t vaddr, uint64_t size, MappingType type) {
|
|
if (size == 0 || UINT64_MAX - vaddr < size) {
|
|
return false;
|
|
}
|
|
const auto end = vaddr + size;
|
|
auto next = m_mapped.lower_bound(vaddr);
|
|
if (next != m_mapped.end() && next->second.vaddr < end) {
|
|
return false;
|
|
}
|
|
if (next != m_mapped.begin()) {
|
|
const auto& previous = std::prev(next)->second;
|
|
if (previous.vaddr + previous.size > vaddr) {
|
|
return false;
|
|
}
|
|
}
|
|
return m_mapped.emplace(vaddr, MappedRegion {vaddr, size, type}).second;
|
|
}
|
|
|
|
bool RemoveMappedUnlocked(uint64_t vaddr, uint64_t size, MappingType type) {
|
|
if (!ContainsMappedUnlocked(vaddr, size, type)) {
|
|
return false;
|
|
}
|
|
const auto end = vaddr + size;
|
|
auto current = vaddr;
|
|
while (current < end) {
|
|
auto it = FindMappedContainingUnlocked(current);
|
|
const auto original = it->second;
|
|
const auto part_end = std::min(end, original.vaddr + original.size);
|
|
m_mapped.erase(it);
|
|
if (original.vaddr < current) {
|
|
const auto left_size = current - original.vaddr;
|
|
EXIT_IF(!AddMappedUnlocked(original.vaddr, left_size, original.type));
|
|
}
|
|
if (part_end < original.vaddr + original.size) {
|
|
const auto right_size = original.vaddr + original.size - part_end;
|
|
EXIT_IF(!AddMappedUnlocked(part_end, right_size, original.type));
|
|
}
|
|
current = part_end;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
static DWORD GetProtect(VirtualMemory::Mode mode) {
|
|
switch (mode) {
|
|
case VirtualMemory::Mode::Read: return PAGE_READONLY;
|
|
case VirtualMemory::Mode::Write:
|
|
case VirtualMemory::Mode::ReadWrite: return PAGE_READWRITE;
|
|
case VirtualMemory::Mode::Execute: return PAGE_EXECUTE;
|
|
case VirtualMemory::Mode::ExecuteRead: return PAGE_EXECUTE_READ;
|
|
case VirtualMemory::Mode::ExecuteWrite:
|
|
case VirtualMemory::Mode::ExecuteReadWrite: return PAGE_EXECUTE_READWRITE;
|
|
case VirtualMemory::Mode::NoAccess:
|
|
default: return PAGE_NOACCESS;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void ReserveGuestRegions() {
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
constexpr uint64_t MinimumRegionSize = 0x1000000;
|
|
const auto process = GetCurrentProcess();
|
|
const auto guest_end = HOST_USER_MAX + 1u;
|
|
auto next_addr = HOST_SYSTEM_MANAGED_MIN;
|
|
|
|
while (next_addr < guest_end) {
|
|
MEMORY_BASIC_INFORMATION info {};
|
|
if (VirtualQuery(reinterpret_cast<const void*>(next_addr), &info, sizeof(info)) == 0) {
|
|
EXIT("failed to inspect guest address space at 0x%016" PRIx64 "\n", next_addr);
|
|
}
|
|
|
|
const auto region_base = reinterpret_cast<uint64_t>(info.BaseAddress);
|
|
const auto region_end =
|
|
std::min<uint64_t>(guest_end, region_base + static_cast<uint64_t>(info.RegionSize));
|
|
const auto reserve_start =
|
|
AlignUp(std::max<uint64_t>(region_base, HOST_SYSTEM_MANAGED_MIN), m_granularity);
|
|
const auto reserve_end = AlignDown(region_end, m_granularity);
|
|
|
|
if (info.State == MEM_FREE && reserve_end > reserve_start &&
|
|
reserve_end - reserve_start > MinimumRegionSize) {
|
|
auto* ptr = VirtualAlloc2(
|
|
process, reinterpret_cast<void*>(reserve_start), reserve_end - reserve_start,
|
|
MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, nullptr, 0);
|
|
if (reinterpret_cast<uint64_t>(ptr) != reserve_start) {
|
|
EXIT("failed to reserve guest address space at 0x%016" PRIx64
|
|
", size 0x%016" PRIx64 "\n",
|
|
reserve_start, reserve_end - reserve_start);
|
|
}
|
|
AddFreeUnlocked(reserve_start, reserve_end - reserve_start);
|
|
m_owned.emplace_back(reserve_start, reserve_end - reserve_start);
|
|
}
|
|
|
|
if (region_end <= next_addr) {
|
|
EXIT("guest address-space scan did not advance at 0x%016" PRIx64 "\n", next_addr);
|
|
}
|
|
next_addr = region_end;
|
|
}
|
|
#else
|
|
const std::array<std::pair<uint64_t, uint64_t>, 3> regions {{
|
|
{HOST_SYSTEM_MANAGED_MIN, HOST_SYSTEM_MANAGED_MAX + 1u},
|
|
{HOST_SYSTEM_RESERVED_MIN, HOST_SYSTEM_RESERVED_MAX + 1u},
|
|
{HOST_USER_MIN, HOST_USER_MAX + 1u},
|
|
}};
|
|
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
|
|
std::vector<std::pair<uint64_t, uint64_t>> occupied;
|
|
FILE* maps = fopen("/proc/self/maps", "r");
|
|
EXIT_IF(maps == nullptr);
|
|
|
|
char line[512];
|
|
while (fgets(line, sizeof(line), maps) != nullptr) {
|
|
unsigned long long mapping_start = 0;
|
|
unsigned long long mapping_end = 0;
|
|
if (sscanf(line, "%llx-%llx", &mapping_start, &mapping_end) == 2) {
|
|
occupied.emplace_back(static_cast<uint64_t>(mapping_start),
|
|
static_cast<uint64_t>(mapping_end));
|
|
}
|
|
}
|
|
fclose(maps);
|
|
|
|
auto reserve_range = [this](uint64_t start, uint64_t end) {
|
|
start = AlignUp(start, PageSize());
|
|
end = AlignDown(end, PageSize());
|
|
if (start == 0 || end <= start) {
|
|
return;
|
|
}
|
|
|
|
const auto size = end - start;
|
|
int flags = MAP_PRIVATE | MAP_ANON | MAP_NORESERVE;
|
|
#if defined(KYTY_LINKED_GUEST_ADDRESS_SPACE)
|
|
flags |= MAP_FIXED;
|
|
#elif defined(MAP_FIXED_NOREPLACE)
|
|
flags |= MAP_FIXED_NOREPLACE;
|
|
#endif
|
|
|
|
void* ptr = mmap(reinterpret_cast<void*>(start), size, PROT_NONE, flags, -1, 0);
|
|
if (ptr == MAP_FAILED || reinterpret_cast<uint64_t>(ptr) != start) {
|
|
if (ptr != MAP_FAILED) {
|
|
munmap(ptr, size);
|
|
}
|
|
return;
|
|
}
|
|
|
|
AddFreeUnlocked(start, size);
|
|
m_owned.emplace_back(start, size);
|
|
};
|
|
|
|
for (const auto& [region_start, region_end]: regions) {
|
|
auto current = region_start;
|
|
|
|
for (const auto& [mapping_start, mapping_end]: occupied) {
|
|
if (mapping_end <= current) {
|
|
continue;
|
|
}
|
|
if (mapping_start >= region_end) {
|
|
break;
|
|
}
|
|
|
|
if (mapping_start > current) {
|
|
reserve_range(current, std::min(mapping_start, region_end));
|
|
}
|
|
|
|
current = std::max(current, mapping_end);
|
|
if (current >= region_end) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (current < region_end) {
|
|
reserve_range(current, region_end);
|
|
}
|
|
}
|
|
#else
|
|
for (const auto& [start, end]: regions) {
|
|
int flags = MAP_PRIVATE | MAP_ANON | MAP_NORESERVE;
|
|
#if defined(KYTY_LINKED_GUEST_ADDRESS_SPACE)
|
|
flags |= MAP_FIXED;
|
|
#elif defined(MAP_FIXED_NOREPLACE)
|
|
flags |= MAP_FIXED_NOREPLACE;
|
|
#endif
|
|
void* ptr = mmap(reinterpret_cast<void*>(start), end - start, PROT_NONE, flags, -1, 0);
|
|
if (ptr == MAP_FAILED || reinterpret_cast<uint64_t>(ptr) != start) {
|
|
if (ptr != MAP_FAILED) {
|
|
munmap(ptr, end - start);
|
|
}
|
|
EXIT("failed to reserve guest address space at 0x%016" PRIx64
|
|
", size 0x%016" PRIx64 "\n",
|
|
start, end - start);
|
|
}
|
|
AddFreeUnlocked(start, end - start);
|
|
m_owned.emplace_back(start, end - start);
|
|
}
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
uint64_t FindFreeAlignedUnlocked(uint64_t search_start, uint64_t search_end, uint64_t size,
|
|
uint64_t alignment) {
|
|
if (search_start >= search_end || size > search_end - search_start) {
|
|
return 0;
|
|
}
|
|
for (const auto& [base, range_size]: m_free) {
|
|
const auto candidate = AlignUp(std::max(base, search_start), alignment);
|
|
if (candidate < search_end && size <= search_end - candidate &&
|
|
GuestFreeRangeContains(base, range_size, candidate, size)) {
|
|
return candidate;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
std::map<uint64_t, uint64_t>::iterator FindContaining(uint64_t vaddr, uint64_t size) {
|
|
auto next = m_free.upper_bound(vaddr);
|
|
if (next == m_free.begin()) {
|
|
return m_free.end();
|
|
}
|
|
auto it = std::prev(next);
|
|
auto end = it->first + it->second;
|
|
return (vaddr >= it->first && vaddr + size <= end ? it : m_free.end());
|
|
}
|
|
|
|
bool SplitUnlocked(std::map<uint64_t, uint64_t>::iterator it, uint64_t vaddr, uint64_t size) {
|
|
const auto base = it->first;
|
|
const auto end = it->first + it->second;
|
|
if (base == vaddr && end == vaddr + size) {
|
|
return true;
|
|
}
|
|
|
|
if (base != vaddr) {
|
|
const auto left_size = vaddr - base;
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
if (VirtualFree(reinterpret_cast<void*>(base), left_size,
|
|
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER) == 0) {
|
|
LOGF_COLOR(Log::Color::Red,
|
|
"\t guest placeholder split failed: base=0x%016" PRIx64
|
|
" size=0x%016" PRIx64 " requested=0x%016" PRIx64
|
|
" request_size=0x%016" PRIx64 " error=0x%08" PRIx32 "\n",
|
|
base, end - base, vaddr, size, static_cast<uint32_t>(GetLastError()));
|
|
return false;
|
|
}
|
|
#endif
|
|
it->second = left_size;
|
|
it = m_free.emplace(vaddr, end - vaddr).first;
|
|
}
|
|
|
|
if (it->second != size) {
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
if (VirtualFree(reinterpret_cast<void*>(it->first), size,
|
|
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER) == 0) {
|
|
LOGF_COLOR(Log::Color::Red,
|
|
"\t guest placeholder split failed: base=0x%016" PRIx64
|
|
" size=0x%016" PRIx64 " requested=0x%016" PRIx64
|
|
" request_size=0x%016" PRIx64 " error=0x%08" PRIx32 "\n",
|
|
it->first, it->second, vaddr, size,
|
|
static_cast<uint32_t>(GetLastError()));
|
|
return false;
|
|
}
|
|
#endif
|
|
const auto right_start = it->first + size;
|
|
const auto right_size = it->second - size;
|
|
it->second = size;
|
|
m_free.emplace(right_start, right_size);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void AddFreeUnlocked(uint64_t vaddr, uint64_t size) {
|
|
if (vaddr == 0 || size == 0) {
|
|
return;
|
|
}
|
|
auto [it, inserted] = m_free.emplace(vaddr, size);
|
|
if (!inserted) {
|
|
it->second = size;
|
|
}
|
|
|
|
auto base = it->first;
|
|
auto end = it->first + it->second;
|
|
|
|
auto first = it;
|
|
while (first != m_free.begin()) {
|
|
auto prev = std::prev(first);
|
|
if (prev->first + prev->second != base) {
|
|
break;
|
|
}
|
|
base = prev->first;
|
|
first = prev;
|
|
}
|
|
|
|
auto last = std::next(it);
|
|
while (last != m_free.end() && end == last->first) {
|
|
end = last->first + last->second;
|
|
last = std::next(last);
|
|
}
|
|
|
|
if (base != it->first || end != it->first + it->second) {
|
|
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
|
if (CoalescePlaceholders(base, end - base)) {
|
|
m_free.erase(first, last);
|
|
m_free.emplace(base, end - base);
|
|
}
|
|
#else
|
|
m_free.erase(first, last);
|
|
m_free.emplace(base, end - base);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void RestorePlaceholder(uint64_t vaddr, uint64_t size) {
|
|
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
|
// The address remains owned for the process lifetime; MAP_FIXED only replaces one of our
|
|
// mappings and never acquires a new guest address.
|
|
void* ptr = mmap(reinterpret_cast<void*>(vaddr), size, PROT_NONE,
|
|
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
|
|
EXIT_IF(ptr == MAP_FAILED || reinterpret_cast<uint64_t>(ptr) != vaddr);
|
|
#else
|
|
(void)vaddr;
|
|
(void)size;
|
|
#endif
|
|
}
|
|
|
|
std::mutex m_mutex;
|
|
std::map<uint64_t, uint64_t> m_free;
|
|
std::map<uint64_t, MappedRegion> m_mapped;
|
|
std::vector<std::pair<uint64_t, uint64_t>> m_owned;
|
|
uint64_t m_granularity = 0x10000;
|
|
GuestBackingStore* m_backing = nullptr;
|
|
};
|