fix(libc): reserve the guest arena at its fixed range around what Windows already placed in it

The arena is always [0x200000000, 0x7200000000). Every free, allocation-granularity-aligned
piece of the range is reserved as a placeholder; what lies between the pieces (thread stacks
and other reservations Windows made before libc starts, unaligned slivers) and the
system-reserved range are holes, used ranges a release never frees. A failed query or
reservation of the range throws.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Michon 2026-10-01 18:50:15 +02:00
parent bc51b337ad
commit 17eefbbbd9
1 changed files with 27 additions and 17 deletions

View File

@ -8,6 +8,8 @@
#include <mutex>
#include <stdexcept>
#include <system_error>
#include <utility>
#include <vector>
#ifdef _WIN32
#include <windows.h>
@ -16,12 +18,10 @@
namespace GuestArena {
namespace {
constexpr std::uintptr_t PreferredBase = 0x0000000200000000ull;
constexpr std::uintptr_t ArenaStart = 0x0000000200000000ull;
constexpr std::uintptr_t SystemReservedStart = 0x00000007FFFFC000ull;
constexpr std::uintptr_t SystemReservedEnd = 0x0000001000000000ull;
constexpr std::size_t MaximumSize = 0x0000007000000000ull;
constexpr std::size_t MinimumSize = 0x0000001000000000ull;
constexpr std::uintptr_t MapAreaEnd = 0x000000FC00000000ull;
constexpr std::size_t ArenaSize = 0x0000007000000000ull;
std::uintptr_t alignUp(std::uintptr_t value, std::size_t alignment) {
return (value + alignment - 1) & ~(static_cast<std::uintptr_t>(alignment) - 1);
@ -83,30 +83,40 @@ public:
if (rangeStart < start) _used.emplace(rangeStart, start);
if (rangeEnd > end) _used.emplace(end, rangeEnd);
}
for (const auto& [holeStart, holeEnd] : _holes) {
if (holeStart < end && start < holeEnd) _used.emplace(holeStart, holeEnd);
}
}
private:
Arena() {
#ifdef _WIN32
// Windows places other reservations randomly, so take the lowest base and largest size that fit.
for (std::uintptr_t base = PreferredBase; base + MinimumSize <= MapAreaEnd; base += MinimumSize) {
for (std::size_t size = MaximumSize; size >= MinimumSize; size /= 2) {
if (base + size > MapAreaEnd) continue;
_writeWatched = std::getenv("APS5_NO_WRITE_WATCH") == nullptr;
void* reserved = WindowsMappings::Get().Reserve(reinterpret_cast<void*>(base), size);
if (!reserved) continue;
_base = reinterpret_cast<std::uintptr_t>(reserved);
_end = _base + size;
if (_base < SystemReservedEnd && SystemReservedStart < _end) _used.emplace(std::max(_base, SystemReservedStart), std::min(_end, SystemReservedEnd));
return;
}
_writeWatched = std::getenv("APS5_NO_WRITE_WATCH") == nullptr;
SYSTEM_INFO system{};
GetSystemInfo(&system);
const std::uintptr_t granularity = system.dwAllocationGranularity;
const std::uintptr_t end = ArenaStart + ArenaSize;
for (std::uintptr_t cursor = ArenaStart; cursor < end;) {
MEMORY_BASIC_INFORMATION info{};
if (VirtualQuery(reinterpret_cast<const void*>(cursor), &info, sizeof(info)) == 0) throw std::system_error(static_cast<int>(GetLastError()), std::system_category(), "query the guest arena range");
const auto regionEnd = std::min(end, reinterpret_cast<std::uintptr_t>(info.BaseAddress) + info.RegionSize);
const auto first = info.State == MEM_FREE ? std::min(regionEnd, alignUp(cursor, granularity)) : regionEnd;
const auto last = std::max(first, regionEnd & ~(granularity - 1));
if (first < last && WindowsMappings::Get().Reserve(reinterpret_cast<void*>(first), last - first) == nullptr) throw std::system_error(static_cast<int>(GetLastError()), std::system_category(), "reserve the guest arena range");
if (cursor < first) _holes.emplace_back(cursor, first);
if (last < regionEnd) _holes.emplace_back(last, regionEnd);
cursor = regionEnd;
}
std::fprintf(stderr, "[memory] guest arena unavailable below 0x%llx\n", static_cast<unsigned long long>(MapAreaEnd));
_holes.emplace_back(SystemReservedStart, SystemReservedEnd);
for (const auto& [holeStart, holeEnd] : _holes) _used.emplace(holeStart, holeEnd);
_base = ArenaStart;
_end = end;
#endif
}
std::mutex _lock;
std::map<std::uintptr_t, std::uintptr_t> _used;
std::vector<std::pair<std::uintptr_t, std::uintptr_t>> _holes;
std::uintptr_t _base = 0;
std::uintptr_t _end = 0;
bool _writeWatched = false;