diff options
Diffstat (limited to 'third_party/base/allocator/partition_allocator/page_allocator.cc')
-rw-r--r-- | third_party/base/allocator/partition_allocator/page_allocator.cc | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/third_party/base/allocator/partition_allocator/page_allocator.cc b/third_party/base/allocator/partition_allocator/page_allocator.cc new file mode 100644 index 0000000000..abe159b727 --- /dev/null +++ b/third_party/base/allocator/partition_allocator/page_allocator.cc @@ -0,0 +1,281 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/base/allocator/partition_allocator/page_allocator.h" + +#include <limits.h> + +#include <atomic> + +#include "third_party/base/allocator/partition_allocator/address_space_randomization.h" +#include "third_party/base/base_export.h" +#include "third_party/base/logging.h" +#include "third_party/build/build_config.h" + +#if defined(OS_POSIX) + +#include <errno.h> +#include <sys/mman.h> + +#ifndef MADV_FREE +#define MADV_FREE MADV_DONTNEED +#endif + +#ifndef MAP_ANONYMOUS +#define MAP_ANONYMOUS MAP_ANON +#endif + +// On POSIX |mmap| uses a nearby address if the hint address is blocked. +static const bool kHintIsAdvisory = true; +static std::atomic<int32_t> s_allocPageErrorCode{0}; + +#elif defined(OS_WIN) + +#include <windows.h> + +// |VirtualAlloc| will fail if allocation at the hint address is blocked. +static const bool kHintIsAdvisory = false; +static std::atomic<int32_t> s_allocPageErrorCode{ERROR_SUCCESS}; + +#else +#error Unknown OS +#endif // defined(OS_POSIX) + +namespace pdfium { +namespace base { + +// This internal function wraps the OS-specific page allocation call: +// |VirtualAlloc| on Windows, and |mmap| on POSIX. +static void* SystemAllocPages( + void* hint, + size_t length, + PageAccessibilityConfiguration page_accessibility) { + DCHECK(!(length & kPageAllocationGranularityOffsetMask)); + DCHECK(!(reinterpret_cast<uintptr_t>(hint) & + kPageAllocationGranularityOffsetMask)); + void* ret; +#if defined(OS_WIN) + DWORD access_flag = + page_accessibility == PageAccessible ? PAGE_READWRITE : PAGE_NOACCESS; + ret = VirtualAlloc(hint, length, MEM_RESERVE | MEM_COMMIT, access_flag); + if (!ret) + s_allocPageErrorCode = GetLastError(); +#else + int access_flag = page_accessibility == PageAccessible + ? (PROT_READ | PROT_WRITE) + : PROT_NONE; + ret = mmap(hint, length, access_flag, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (ret == MAP_FAILED) { + s_allocPageErrorCode = errno; + ret = 0; + } +#endif + return ret; +} + +// Trims base to given length and alignment. Windows returns null on failure and +// frees base. +static void* TrimMapping(void* base, + size_t base_length, + size_t trim_length, + uintptr_t align, + PageAccessibilityConfiguration page_accessibility) { + size_t pre_slack = reinterpret_cast<uintptr_t>(base) & (align - 1); + if (pre_slack) + pre_slack = align - pre_slack; + size_t post_slack = base_length - pre_slack - trim_length; + DCHECK(base_length >= trim_length || pre_slack || post_slack); + DCHECK(pre_slack < base_length); + DCHECK(post_slack < base_length); + void* ret = base; + +#if defined(OS_POSIX) // On POSIX we can resize the allocation run. + (void)page_accessibility; + if (pre_slack) { + int res = munmap(base, pre_slack); + CHECK(!res); + ret = reinterpret_cast<char*>(base) + pre_slack; + } + if (post_slack) { + int res = munmap(reinterpret_cast<char*>(ret) + trim_length, post_slack); + CHECK(!res); + } +#else // On Windows we can't resize the allocation run. + if (pre_slack || post_slack) { + ret = reinterpret_cast<char*>(base) + pre_slack; + FreePages(base, base_length); + ret = SystemAllocPages(ret, trim_length, page_accessibility); + } +#endif + + return ret; +} + +void* AllocPages(void* address, + size_t length, + size_t align, + PageAccessibilityConfiguration page_accessibility) { + DCHECK(length >= kPageAllocationGranularity); + DCHECK(!(length & kPageAllocationGranularityOffsetMask)); + DCHECK(align >= kPageAllocationGranularity); + DCHECK(!(align & kPageAllocationGranularityOffsetMask)); + DCHECK(!(reinterpret_cast<uintptr_t>(address) & + kPageAllocationGranularityOffsetMask)); + uintptr_t align_offset_mask = align - 1; + uintptr_t align_base_mask = ~align_offset_mask; + DCHECK(!(reinterpret_cast<uintptr_t>(address) & align_offset_mask)); + + // If the client passed null as the address, choose a good one. + if (!address) { + address = GetRandomPageBase(); + address = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(address) & + align_base_mask); + } + + // First try to force an exact-size, aligned allocation from our random base. + for (int count = 0; count < 3; ++count) { + void* ret = SystemAllocPages(address, length, page_accessibility); + if (kHintIsAdvisory || ret) { + // If the alignment is to our liking, we're done. + if (!(reinterpret_cast<uintptr_t>(ret) & align_offset_mask)) + return ret; + FreePages(ret, length); +#if defined(ARCH_CPU_32_BITS) + address = reinterpret_cast<void*>( + (reinterpret_cast<uintptr_t>(ret) + align) & align_base_mask); +#endif + } else if (!address) { // We know we're OOM when an unhinted allocation + // fails. + return nullptr; + } else { +#if defined(ARCH_CPU_32_BITS) + address = reinterpret_cast<char*>(address) + align; +#endif + } + +#if !defined(ARCH_CPU_32_BITS) + // Keep trying random addresses on systems that have a large address space. + address = GetRandomPageBase(); + address = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(address) & + align_base_mask); +#endif + } + + // Map a larger allocation so we can force alignment, but continue randomizing + // only on 64-bit POSIX. + size_t try_length = length + (align - kPageAllocationGranularity); + CHECK(try_length >= length); + void* ret; + + do { + // Don't continue to burn cycles on mandatory hints (Windows). + address = kHintIsAdvisory ? GetRandomPageBase() : nullptr; + ret = SystemAllocPages(address, try_length, page_accessibility); + // The retries are for Windows, where a race can steal our mapping on + // resize. + } while (ret && + (ret = TrimMapping(ret, try_length, length, align, + page_accessibility)) == nullptr); + + return ret; +} + +void FreePages(void* address, size_t length) { + DCHECK(!(reinterpret_cast<uintptr_t>(address) & + kPageAllocationGranularityOffsetMask)); + DCHECK(!(length & kPageAllocationGranularityOffsetMask)); +#if defined(OS_POSIX) + int ret = munmap(address, length); + CHECK(!ret); +#else + BOOL ret = VirtualFree(address, 0, MEM_RELEASE); + CHECK(ret); +#endif +} + +void SetSystemPagesInaccessible(void* address, size_t length) { + DCHECK(!(length & kSystemPageOffsetMask)); +#if defined(OS_POSIX) + int ret = mprotect(address, length, PROT_NONE); + CHECK(!ret); +#else + BOOL ret = VirtualFree(address, length, MEM_DECOMMIT); + CHECK(ret); +#endif +} + +bool SetSystemPagesAccessible(void* address, size_t length) { + DCHECK(!(length & kSystemPageOffsetMask)); +#if defined(OS_POSIX) + return !mprotect(address, length, PROT_READ | PROT_WRITE); +#else + return !!VirtualAlloc(address, length, MEM_COMMIT, PAGE_READWRITE); +#endif +} + +void DecommitSystemPages(void* address, size_t length) { + DCHECK(!(length & kSystemPageOffsetMask)); +#if defined(OS_POSIX) + int ret = madvise(address, length, MADV_FREE); + if (ret != 0 && errno == EINVAL) { + // MADV_FREE only works on Linux 4.5+ . If request failed, + // retry with older MADV_DONTNEED . Note that MADV_FREE + // being defined at compile time doesn't imply runtime support. + ret = madvise(address, length, MADV_DONTNEED); + } + CHECK(!ret); +#else + SetSystemPagesInaccessible(address, length); +#endif +} + +void RecommitSystemPages(void* address, size_t length) { + DCHECK(!(length & kSystemPageOffsetMask)); +#if defined(OS_POSIX) + (void)address; +#else + CHECK(SetSystemPagesAccessible(address, length)); +#endif +} + +void DiscardSystemPages(void* address, size_t length) { + DCHECK(!(length & kSystemPageOffsetMask)); +#if defined(OS_POSIX) + // On POSIX, the implementation detail is that discard and decommit are the + // same, and lead to pages that are returned to the system immediately and + // get replaced with zeroed pages when touched. So we just call + // DecommitSystemPages() here to avoid code duplication. + DecommitSystemPages(address, length); +#else + // On Windows discarded pages are not returned to the system immediately and + // not guaranteed to be zeroed when returned to the application. + using DiscardVirtualMemoryFunction = + DWORD(WINAPI*)(PVOID virtualAddress, SIZE_T size); + static DiscardVirtualMemoryFunction discard_virtual_memory = + reinterpret_cast<DiscardVirtualMemoryFunction>(-1); + if (discard_virtual_memory == + reinterpret_cast<DiscardVirtualMemoryFunction>(-1)) + discard_virtual_memory = + reinterpret_cast<DiscardVirtualMemoryFunction>(GetProcAddress( + GetModuleHandle(L"Kernel32.dll"), "DiscardVirtualMemory")); + // Use DiscardVirtualMemory when available because it releases faster than + // MEM_RESET. + DWORD ret = 1; + if (discard_virtual_memory) + ret = discard_virtual_memory(address, length); + // DiscardVirtualMemory is buggy in Win10 SP0, so fall back to MEM_RESET on + // failure. + if (ret) { + void* ret = VirtualAlloc(address, length, MEM_RESET, PAGE_READWRITE); + CHECK(ret); + } +#endif +} + +uint32_t GetAllocPageErrorCode() { + return s_allocPageErrorCode; +} + +} // namespace base +} // namespace pdfium |