tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 4dcb947682526e27fbb8196b1bf8634657bc8df5
parent 6c57baa9339508956757b5ffaad69cc8b59cedbd
Author: Paul Bone <paul@bone.id.au>
Date:   Tue, 18 Nov 2025 00:46:45 +0000

Bug 1993227 - Reserve the PHC memory range without allocating RAM r=glandium DONTBUILD

Differential Revision: https://phabricator.services.mozilla.com/D272117

Diffstat:
Mmemory/build/Chunk.cpp | 57++++++++++++++++++++++++++++++++++++++-------------------
Mmemory/build/Chunk.h | 12++++++++++++
Mmemory/build/PHC.cpp | 37++++++++++---------------------------
3 files changed, 60 insertions(+), 46 deletions(-)

diff --git a/memory/build/Chunk.cpp b/memory/build/Chunk.cpp @@ -208,10 +208,16 @@ static inline void* _mmap(void* addr, size_t length, int prot, int flags, #ifdef XP_WIN -static void* pages_map(void* aAddr, size_t aSize) { - void* ret = nullptr; - ret = MozVirtualAlloc(aAddr, aSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); - return ret; +static void* pages_map(void* aAddr, size_t aSize, ShouldCommit should_commit) { + switch (should_commit) { + case ReserveAndCommit: + return MozVirtualAlloc(aAddr, aSize, MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE); + case ReserveOnly: + // When not requesting committed memory we can skip MozVirtualAlloc and + // its stall behaviour. + return VirtualAlloc(aAddr, aSize, MEM_RESERVE, PAGE_NOACCESS); + } } static void pages_unmap(void* aAddr, size_t aSize) { @@ -232,7 +238,7 @@ static void pages_unmap(void* aAddr, size_t aSize) { } } -static void* pages_map(void* aAddr, size_t aSize) { +static void* pages_map(void* aAddr, size_t aSize, ShouldCommit should_commit) { void* ret; # if defined(__ia64__) || \ (defined(__sparc__) && defined(__arch64__) && defined(__linux__)) @@ -263,8 +269,9 @@ static void* pages_map(void* aAddr, size_t aSize) { void* region = MAP_FAILED; for (hint = start; region == MAP_FAILED && hint + aSize <= end; hint += kChunkSize) { - region = mmap((void*)hint, aSize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON, -1, 0); + region = + mmap((void*)hint, aSize, committed ? PROT_READ | PROT_WRITE : PROT_NONE, + MAP_PRIVATE | MAP_ANON, -1, 0); if (region != MAP_FAILED) { if (((size_t)region + (aSize - 1)) & 0xffff800000000000) { if (munmap(region, aSize)) { @@ -278,8 +285,10 @@ static void* pages_map(void* aAddr, size_t aSize) { # else // We don't use MAP_FIXED here, because it can cause the *replacement* // of existing mappings, and we only want to create new mappings. - ret = - mmap(aAddr, aSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + ret = mmap( + aAddr, aSize, + should_commit == ReserveAndCommit ? PROT_READ | PROT_WRITE : PROT_NONE, + MAP_PRIVATE | MAP_ANON, -1, 0); MOZ_ASSERT(ret); # endif if (ret == MAP_FAILED) { @@ -399,7 +408,7 @@ static bool pages_purge(void* addr, size_t length, bool force_zero) { // cherry-picked from upstream jemalloc 3.4.1 to fix Mozilla bug 956501. static void* pages_trim(void* addr, size_t alloc_size, size_t leadsize, - size_t size) { + size_t size, ShouldCommit should_commit) { void* ret = (void*)((uintptr_t)addr + leadsize); MOZ_ASSERT(alloc_size >= leadsize + size); @@ -408,7 +417,7 @@ static void* pages_trim(void* addr, size_t alloc_size, size_t leadsize, void* new_addr; pages_unmap(addr, alloc_size); - new_addr = pages_map(ret, size); + new_addr = pages_map(ret, size, should_commit); if (new_addr == ret) { return ret; } @@ -432,7 +441,8 @@ static void* pages_trim(void* addr, size_t alloc_size, size_t leadsize, #endif } -static void* pages_mmap_aligned_slow(size_t size, size_t alignment) { +static void* pages_mmap_aligned_slow(size_t size, size_t alignment, + ShouldCommit should_commit) { void *ret, *pages; size_t alloc_size, leadsize; @@ -442,20 +452,21 @@ static void* pages_mmap_aligned_slow(size_t size, size_t alignment) { return nullptr; } do { - pages = pages_map(nullptr, alloc_size); + pages = pages_map(nullptr, alloc_size, should_commit); if (!pages) { return nullptr; } leadsize = ALIGNMENT_CEILING((uintptr_t)pages, alignment) - (uintptr_t)pages; - ret = pages_trim(pages, alloc_size, leadsize, size); + ret = pages_trim(pages, alloc_size, leadsize, size, should_commit); } while (!ret); MOZ_ASSERT(ret); return ret; } -static void* pages_mmap_aligned(size_t size, size_t alignment) { +void* pages_mmap_aligned(size_t size, size_t alignment, + ShouldCommit should_commit) { void* ret; size_t offset; @@ -469,14 +480,14 @@ static void* pages_mmap_aligned(size_t size, size_t alignment) { // Optimistically try mapping precisely the right amount before falling // back to the slow method, with the expectation that the optimistic // approach works most of the time. - ret = pages_map(nullptr, size); + ret = pages_map(nullptr, size, should_commit); if (!ret) { return nullptr; } offset = ALIGNMENT_ADDR2OFFSET(ret, alignment); if (offset != 0) { pages_unmap(ret, size); - return pages_mmap_aligned_slow(size, alignment); + return pages_mmap_aligned_slow(size, alignment, should_commit); } MOZ_ASSERT(ret); @@ -619,9 +630,17 @@ void chunk_dealloc(void* aChunk, size_t aSize, ChunkType aType) { size_t recycle_remaining = gRecycleLimit - recycled_so_far; size_t to_recycle; if (aSize > recycle_remaining) { +#ifndef XP_WIN to_recycle = recycle_remaining; // Drop pages that would overflow the recycle limit - pages_trim(aChunk, aSize, 0, to_recycle); + pages_trim(aChunk, aSize, 0, to_recycle, ReserveAndCommit); +#else + // On windows pages_trim unallocates and reallocates the whole + // chunk, there's no point doing that during recycling so instead we + // fail. + pages_unmap(aChunk, aSize); + return; +#endif } else { to_recycle = aSize; } @@ -727,7 +746,7 @@ void* chunk_alloc(size_t aSize, size_t aAlignment, bool aBase) { ret = chunk_recycle(aSize, aAlignment); } if (!ret) { - ret = pages_mmap_aligned(aSize, aAlignment); + ret = pages_mmap_aligned(aSize, aAlignment, ReserveAndCommit); } if (ret && !aBase) { if (!gChunkRTree.Set(ret, ret)) { diff --git a/memory/build/Chunk.h b/memory/build/Chunk.h @@ -200,4 +200,16 @@ extern mozilla::Atomic<size_t> gRecycledSize; extern AddressRadixTree<(sizeof(void*) << 3) - LOG2(kChunkSize)> gChunkRTree; +enum ShouldCommit { + // Reserve address space only, accessing the mapping will crash. + ReserveOnly, + + // Reserve the address space and populate it with "valid" page mappings. + // On windows this will commit memory, on Linux it populate memory as its + // accessed (with overcommit behaviour). + ReserveAndCommit, +}; + +void* pages_mmap_aligned(size_t size, size_t alignment, ShouldCommit committed); + #endif /* ! CHUNK_H */ diff --git a/memory/build/PHC.cpp b/memory/build/PHC.cpp @@ -110,6 +110,7 @@ #endif #include "mozjemalloc.h" +#include "Chunk.h" #include "FdPrintf.h" #include "Mutex.h" #include "mozilla/Assertions.h" @@ -609,30 +610,15 @@ class PHCRegion { // The memory allocated here is never freed, because it would happen at // process termination when it would be of little use. - // We can rely on jemalloc's behaviour that when it allocates memory aligned - // with its own chunk size it will over-allocate and guarantee that the - // memory after the end of our allocation, but before the next chunk, is - // decommitted and inaccessible. Elsewhere in PHC we assume that we own - // that page (so that memory errors in it get caught by PHC). But in this - // function we subtract one page from the end. - size_t jemalloc_allocation = kPhcVirtualReservation - kPageSize; - void* pages = MozJemalloc::memalign(kPhcAlign, jemalloc_allocation); + // On Windows in particular we want to control how the memory is initially + // reserved. Windows pages memory in immediately which creates performance + // problems and could affect stability. + void* pages = + pages_mmap_aligned(kPhcVirtualReservation, kPhcAlign, ReserveOnly); if (!pages) { return false; } - // Make the pages inaccessible. -#ifdef XP_WIN - if (!VirtualFree(pages, jemalloc_allocation, MEM_DECOMMIT)) { - return false; - } -#else - if (mmap(pages, jemalloc_allocation, PROT_NONE, - MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0) == MAP_FAILED) { - return false; - } -#endif - mPagesStart = static_cast<uint8_t*>(pages); mPagesLimit = mPagesStart + kPhcVirtualReservation; Log("AllocVirtualAddresses at %p..%p\n", mPagesStart, mPagesLimit); @@ -2013,16 +1999,13 @@ inline void MozJemallocPHC::jemalloc_stats_internal( return; } - // We allocate our memory from jemalloc so it has already counted our memory - // usage within "mapped" and "allocated", we must subtract the memory we - // allocated from jemalloc from allocated before adding in only the parts that - // we have allocated out to Firefox. - // Don't count the final guard page which jemalloc added. - aStats->allocated -= PHC::sRegion.ReservedBytes(); - + // Add PHC's memory usage to the allocator's. phc::MemoryUsage mem_info; PHC::sPHC->GetMemoryUsage(mem_info); aStats->allocated += mem_info.mAllocatedBytes; + aStats->waste += mem_info.mFragmentationBytes; + aStats->mapped += PHC::sRegion.ReservedBytes() - mem_info.mAllocatedBytes - + mem_info.mFragmentationBytes; // guards is the gap between `allocated` and `mapped`. In some ways this // almost fits into aStats->wasted since it feels like wasted memory. However