Home | History | Annotate | Download | only in partition_allocator
      1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "third_party/base/allocator/partition_allocator/page_allocator.h"
      6 
      7 #include <limits.h>
      8 
      9 #include <atomic>
     10 
     11 #include "third_party/base/allocator/partition_allocator/address_space_randomization.h"
     12 #include "third_party/base/base_export.h"
     13 #include "third_party/base/logging.h"
     14 #include "third_party/build/build_config.h"
     15 
     16 #if defined(OS_POSIX)
     17 
     18 #include <errno.h>
     19 #include <sys/mman.h>
     20 
     21 #ifndef MADV_FREE
     22 #define MADV_FREE MADV_DONTNEED
     23 #endif
     24 
     25 #ifndef MAP_ANONYMOUS
     26 #define MAP_ANONYMOUS MAP_ANON
     27 #endif
     28 
     29 // On POSIX |mmap| uses a nearby address if the hint address is blocked.
     30 static const bool kHintIsAdvisory = true;
     31 static std::atomic<int32_t> s_allocPageErrorCode{0};
     32 
     33 #elif defined(OS_WIN)
     34 
     35 #include <windows.h>
     36 
     37 // |VirtualAlloc| will fail if allocation at the hint address is blocked.
     38 static const bool kHintIsAdvisory = false;
     39 static std::atomic<int32_t> s_allocPageErrorCode{ERROR_SUCCESS};
     40 
     41 #else
     42 #error Unknown OS
     43 #endif  // defined(OS_POSIX)
     44 
     45 namespace pdfium {
     46 namespace base {
     47 
     48 // This internal function wraps the OS-specific page allocation call:
     49 // |VirtualAlloc| on Windows, and |mmap| on POSIX.
     50 static void* SystemAllocPages(
     51     void* hint,
     52     size_t length,
     53     PageAccessibilityConfiguration page_accessibility) {
     54   DCHECK(!(length & kPageAllocationGranularityOffsetMask));
     55   DCHECK(!(reinterpret_cast<uintptr_t>(hint) &
     56            kPageAllocationGranularityOffsetMask));
     57   void* ret;
     58 #if defined(OS_WIN)
     59   DWORD access_flag =
     60       page_accessibility == PageAccessible ? PAGE_READWRITE : PAGE_NOACCESS;
     61   ret = VirtualAlloc(hint, length, MEM_RESERVE | MEM_COMMIT, access_flag);
     62   if (!ret)
     63     s_allocPageErrorCode = GetLastError();
     64 #else
     65   int access_flag = page_accessibility == PageAccessible
     66                         ? (PROT_READ | PROT_WRITE)
     67                         : PROT_NONE;
     68   ret = mmap(hint, length, access_flag, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
     69   if (ret == MAP_FAILED) {
     70     s_allocPageErrorCode = errno;
     71     ret = 0;
     72   }
     73 #endif
     74   return ret;
     75 }
     76 
     77 // Trims base to given length and alignment. Windows returns null on failure and
     78 // frees base.
     79 static void* TrimMapping(void* base,
     80                          size_t base_length,
     81                          size_t trim_length,
     82                          uintptr_t align,
     83                          PageAccessibilityConfiguration page_accessibility) {
     84   size_t pre_slack = reinterpret_cast<uintptr_t>(base) & (align - 1);
     85   if (pre_slack)
     86     pre_slack = align - pre_slack;
     87   size_t post_slack = base_length - pre_slack - trim_length;
     88   DCHECK(base_length >= trim_length || pre_slack || post_slack);
     89   DCHECK(pre_slack < base_length);
     90   DCHECK(post_slack < base_length);
     91   void* ret = base;
     92 
     93 #if defined(OS_POSIX)  // On POSIX we can resize the allocation run.
     94   (void)page_accessibility;
     95   if (pre_slack) {
     96     int res = munmap(base, pre_slack);
     97     CHECK(!res);
     98     ret = reinterpret_cast<char*>(base) + pre_slack;
     99   }
    100   if (post_slack) {
    101     int res = munmap(reinterpret_cast<char*>(ret) + trim_length, post_slack);
    102     CHECK(!res);
    103   }
    104 #else  // On Windows we can't resize the allocation run.
    105   if (pre_slack || post_slack) {
    106     ret = reinterpret_cast<char*>(base) + pre_slack;
    107     FreePages(base, base_length);
    108     ret = SystemAllocPages(ret, trim_length, page_accessibility);
    109   }
    110 #endif
    111 
    112   return ret;
    113 }
    114 
    115 void* AllocPages(void* address,
    116                  size_t length,
    117                  size_t align,
    118                  PageAccessibilityConfiguration page_accessibility) {
    119   DCHECK(length >= kPageAllocationGranularity);
    120   DCHECK(!(length & kPageAllocationGranularityOffsetMask));
    121   DCHECK(align >= kPageAllocationGranularity);
    122   DCHECK(!(align & kPageAllocationGranularityOffsetMask));
    123   DCHECK(!(reinterpret_cast<uintptr_t>(address) &
    124            kPageAllocationGranularityOffsetMask));
    125   uintptr_t align_offset_mask = align - 1;
    126   uintptr_t align_base_mask = ~align_offset_mask;
    127   DCHECK(!(reinterpret_cast<uintptr_t>(address) & align_offset_mask));
    128 
    129   // If the client passed null as the address, choose a good one.
    130   if (!address) {
    131     address = GetRandomPageBase();
    132     address = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(address) &
    133                                       align_base_mask);
    134   }
    135 
    136   // First try to force an exact-size, aligned allocation from our random base.
    137   for (int count = 0; count < 3; ++count) {
    138     void* ret = SystemAllocPages(address, length, page_accessibility);
    139     if (kHintIsAdvisory || ret) {
    140       // If the alignment is to our liking, we're done.
    141       if (!(reinterpret_cast<uintptr_t>(ret) & align_offset_mask))
    142         return ret;
    143       FreePages(ret, length);
    144 #if defined(ARCH_CPU_32_BITS)
    145       address = reinterpret_cast<void*>(
    146           (reinterpret_cast<uintptr_t>(ret) + align) & align_base_mask);
    147 #endif
    148     } else if (!address) {  // We know we're OOM when an unhinted allocation
    149                             // fails.
    150       return nullptr;
    151     } else {
    152 #if defined(ARCH_CPU_32_BITS)
    153       address = reinterpret_cast<char*>(address) + align;
    154 #endif
    155     }
    156 
    157 #if !defined(ARCH_CPU_32_BITS)
    158     // Keep trying random addresses on systems that have a large address space.
    159     address = GetRandomPageBase();
    160     address = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(address) &
    161                                       align_base_mask);
    162 #endif
    163   }
    164 
    165   // Map a larger allocation so we can force alignment, but continue randomizing
    166   // only on 64-bit POSIX.
    167   size_t try_length = length + (align - kPageAllocationGranularity);
    168   CHECK(try_length >= length);
    169   void* ret;
    170 
    171   do {
    172     // Don't continue to burn cycles on mandatory hints (Windows).
    173     address = kHintIsAdvisory ? GetRandomPageBase() : nullptr;
    174     ret = SystemAllocPages(address, try_length, page_accessibility);
    175     // The retries are for Windows, where a race can steal our mapping on
    176     // resize.
    177   } while (ret &&
    178            (ret = TrimMapping(ret, try_length, length, align,
    179                               page_accessibility)) == nullptr);
    180 
    181   return ret;
    182 }
    183 
    184 void FreePages(void* address, size_t length) {
    185   DCHECK(!(reinterpret_cast<uintptr_t>(address) &
    186            kPageAllocationGranularityOffsetMask));
    187   DCHECK(!(length & kPageAllocationGranularityOffsetMask));
    188 #if defined(OS_POSIX)
    189   int ret = munmap(address, length);
    190   CHECK(!ret);
    191 #else
    192   BOOL ret = VirtualFree(address, 0, MEM_RELEASE);
    193   CHECK(ret);
    194 #endif
    195 }
    196 
    197 void SetSystemPagesInaccessible(void* address, size_t length) {
    198   DCHECK(!(length & kSystemPageOffsetMask));
    199 #if defined(OS_POSIX)
    200   int ret = mprotect(address, length, PROT_NONE);
    201   CHECK(!ret);
    202 #else
    203   BOOL ret = VirtualFree(address, length, MEM_DECOMMIT);
    204   CHECK(ret);
    205 #endif
    206 }
    207 
    208 bool SetSystemPagesAccessible(void* address, size_t length) {
    209   DCHECK(!(length & kSystemPageOffsetMask));
    210 #if defined(OS_POSIX)
    211   return !mprotect(address, length, PROT_READ | PROT_WRITE);
    212 #else
    213   return !!VirtualAlloc(address, length, MEM_COMMIT, PAGE_READWRITE);
    214 #endif
    215 }
    216 
    217 void DecommitSystemPages(void* address, size_t length) {
    218   DCHECK(!(length & kSystemPageOffsetMask));
    219 #if defined(OS_POSIX)
    220   int ret = madvise(address, length, MADV_FREE);
    221   if (ret != 0 && errno == EINVAL) {
    222     // MADV_FREE only works on Linux 4.5+ . If request failed,
    223     // retry with older MADV_DONTNEED . Note that MADV_FREE
    224     // being defined at compile time doesn't imply runtime support.
    225     ret = madvise(address, length, MADV_DONTNEED);
    226   }
    227   CHECK(!ret);
    228 #else
    229   SetSystemPagesInaccessible(address, length);
    230 #endif
    231 }
    232 
    233 void RecommitSystemPages(void* address, size_t length) {
    234   DCHECK(!(length & kSystemPageOffsetMask));
    235 #if defined(OS_POSIX)
    236   (void)address;
    237 #else
    238   CHECK(SetSystemPagesAccessible(address, length));
    239 #endif
    240 }
    241 
    242 void DiscardSystemPages(void* address, size_t length) {
    243   DCHECK(!(length & kSystemPageOffsetMask));
    244 #if defined(OS_POSIX)
    245   // On POSIX, the implementation detail is that discard and decommit are the
    246   // same, and lead to pages that are returned to the system immediately and
    247   // get replaced with zeroed pages when touched. So we just call
    248   // DecommitSystemPages() here to avoid code duplication.
    249   DecommitSystemPages(address, length);
    250 #else
    251   // On Windows discarded pages are not returned to the system immediately and
    252   // not guaranteed to be zeroed when returned to the application.
    253   using DiscardVirtualMemoryFunction =
    254       DWORD(WINAPI*)(PVOID virtualAddress, SIZE_T size);
    255   static DiscardVirtualMemoryFunction discard_virtual_memory =
    256       reinterpret_cast<DiscardVirtualMemoryFunction>(-1);
    257   if (discard_virtual_memory ==
    258       reinterpret_cast<DiscardVirtualMemoryFunction>(-1))
    259     discard_virtual_memory =
    260         reinterpret_cast<DiscardVirtualMemoryFunction>(GetProcAddress(
    261             GetModuleHandle(L"Kernel32.dll"), "DiscardVirtualMemory"));
    262   // Use DiscardVirtualMemory when available because it releases faster than
    263   // MEM_RESET.
    264   DWORD ret = 1;
    265   if (discard_virtual_memory)
    266     ret = discard_virtual_memory(address, length);
    267   // DiscardVirtualMemory is buggy in Win10 SP0, so fall back to MEM_RESET on
    268   // failure.
    269   if (ret) {
    270     void* ret = VirtualAlloc(address, length, MEM_RESET, PAGE_READWRITE);
    271     CHECK(ret);
    272   }
    273 #endif
    274 }
    275 
    276 uint32_t GetAllocPageErrorCode() {
    277   return s_allocPageErrorCode;
    278 }
    279 
    280 }  // namespace base
    281 }  // namespace pdfium
    282