1 // Copyright (c) 2018 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 #ifndef THIRD_PARTY_BASE_ALLOCATOR_PARTITION_ALLOCATOR_PAGE_ALLOCATOR_INTERNALS_POSIX_H_
6 #define THIRD_PARTY_BASE_ALLOCATOR_PARTITION_ALLOCATOR_PAGE_ALLOCATOR_INTERNALS_POSIX_H_
7
8 #include <errno.h>
9 #include <sys/mman.h>
10
11 #include "build/build_config.h"
12
13 #if defined(OS_APPLE)
14 #include <mach/mach.h>
15 #endif
16 #if defined(OS_ANDROID)
17 #include <sys/prctl.h>
18 #endif
19 #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_BSD)
20 #include <sys/resource.h>
21
22 #include <algorithm>
23 #endif
24
25 #include "third_party/base/allocator/partition_allocator/page_allocator.h"
26 #include "third_party/base/check.h"
27 #include "third_party/base/notreached.h"
28
29 #ifndef MAP_ANONYMOUS
30 #define MAP_ANONYMOUS MAP_ANON
31 #endif
32
33 namespace pdfium {
34 namespace base {
35
36 #if defined(OS_ANDROID)
37 namespace {
PageTagToName(PageTag tag)38 const char* PageTagToName(PageTag tag) {
39 // Important: All the names should be string literals. As per prctl.h in
40 // //third_party/android_ndk the kernel keeps a pointer to the name instead
41 // of copying it.
42 //
43 // Having the name in .rodata ensures that the pointer remains valid as
44 // long as the mapping is alive.
45 switch (tag) {
46 case PageTag::kBlinkGC:
47 return "blink_gc";
48 case PageTag::kPartitionAlloc:
49 return "partition_alloc";
50 case PageTag::kChromium:
51 return "chromium";
52 case PageTag::kV8:
53 return "v8";
54 default:
55 DCHECK(false);
56 return "";
57 }
58 }
59 } // namespace
60 #endif // defined(OS_ANDROID)
61
62 // |mmap| uses a nearby address if the hint address is blocked.
63 constexpr bool kHintIsAdvisory = true;
64 std::atomic<int32_t> s_allocPageErrorCode{0};
65
GetAccessFlags(PageAccessibilityConfiguration accessibility)66 int GetAccessFlags(PageAccessibilityConfiguration accessibility) {
67 switch (accessibility) {
68 case PageRead:
69 return PROT_READ;
70 case PageReadWrite:
71 return PROT_READ | PROT_WRITE;
72 case PageReadExecute:
73 return PROT_READ | PROT_EXEC;
74 case PageReadWriteExecute:
75 return PROT_READ | PROT_WRITE | PROT_EXEC;
76 default:
77 NOTREACHED();
78 FALLTHROUGH;
79 case PageInaccessible:
80 return PROT_NONE;
81 }
82 }
83
SystemAllocPagesInternal(void * hint,size_t length,PageAccessibilityConfiguration accessibility,PageTag page_tag,bool commit)84 void* SystemAllocPagesInternal(void* hint,
85 size_t length,
86 PageAccessibilityConfiguration accessibility,
87 PageTag page_tag,
88 bool commit) {
89 #if defined(OS_APPLE)
90 // Use a custom tag to make it easier to distinguish Partition Alloc regions
91 // in vmmap(1). Tags between 240-255 are supported.
92 DCHECK(PageTag::kFirst <= page_tag);
93 DCHECK(PageTag::kLast >= page_tag);
94 int fd = VM_MAKE_TAG(static_cast<int>(page_tag));
95 #else
96 int fd = -1;
97 #endif
98
99 int access_flag = GetAccessFlags(accessibility);
100 int map_flags = MAP_ANONYMOUS | MAP_PRIVATE;
101
102 // TODO(https://crbug.com/927411): Remove once Fuchsia uses a native page
103 // allocator, rather than relying on POSIX compatibility.
104 #if defined(OS_FUCHSIA)
105 if (page_tag == PageTag::kV8) {
106 map_flags |= MAP_JIT;
107 }
108 #endif
109
110 void* ret = mmap(hint, length, access_flag, map_flags, fd, 0);
111 if (ret == MAP_FAILED) {
112 s_allocPageErrorCode = errno;
113 ret = nullptr;
114 }
115
116 #if defined(OS_ANDROID)
117 // On Android, anonymous mappings can have a name attached to them. This is
118 // useful for debugging, and double-checking memory attribution.
119 if (ret) {
120 // No error checking on purpose, testing only.
121 prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, ret, length,
122 PageTagToName(page_tag));
123 }
124 #endif
125
126 return ret;
127 }
128
TrimMappingInternal(void * base,size_t base_length,size_t trim_length,PageAccessibilityConfiguration accessibility,bool commit,size_t pre_slack,size_t post_slack)129 void* TrimMappingInternal(void* base,
130 size_t base_length,
131 size_t trim_length,
132 PageAccessibilityConfiguration accessibility,
133 bool commit,
134 size_t pre_slack,
135 size_t post_slack) {
136 void* ret = base;
137 // We can resize the allocation run. Release unneeded memory before and after
138 // the aligned range.
139 if (pre_slack) {
140 int res = munmap(base, pre_slack);
141 CHECK(!res);
142 ret = reinterpret_cast<char*>(base) + pre_slack;
143 }
144 if (post_slack) {
145 int res = munmap(reinterpret_cast<char*>(ret) + trim_length, post_slack);
146 CHECK(!res);
147 }
148 return ret;
149 }
150
TrySetSystemPagesAccessInternal(void * address,size_t length,PageAccessibilityConfiguration accessibility)151 bool TrySetSystemPagesAccessInternal(
152 void* address,
153 size_t length,
154 PageAccessibilityConfiguration accessibility) {
155 return 0 == mprotect(address, length, GetAccessFlags(accessibility));
156 }
157
SetSystemPagesAccessInternal(void * address,size_t length,PageAccessibilityConfiguration accessibility)158 void SetSystemPagesAccessInternal(
159 void* address,
160 size_t length,
161 PageAccessibilityConfiguration accessibility) {
162 CHECK_EQ(0, mprotect(address, length, GetAccessFlags(accessibility)));
163 }
164
FreePagesInternal(void * address,size_t length)165 void FreePagesInternal(void* address, size_t length) {
166 CHECK(!munmap(address, length));
167 }
168
DecommitSystemPagesInternal(void * address,size_t length)169 void DecommitSystemPagesInternal(void* address, size_t length) {
170 // In POSIX, there is no decommit concept. Discarding is an effective way of
171 // implementing the Windows semantics where the OS is allowed to not swap the
172 // pages in the region.
173 //
174 // TODO(ajwong): Also explore setting PageInaccessible to make the protection
175 // semantics consistent between Windows and POSIX. This might have a perf cost
176 // though as both decommit and recommit would incur an extra syscall.
177 // http://crbug.com/766882
178 DiscardSystemPages(address, length);
179 }
180
RecommitSystemPagesInternal(void * address,size_t length,PageAccessibilityConfiguration accessibility)181 bool RecommitSystemPagesInternal(void* address,
182 size_t length,
183 PageAccessibilityConfiguration accessibility) {
184 #if defined(OS_APPLE)
185 // On macOS, to update accounting, we need to make another syscall. For more
186 // details, see https://crbug.com/823915.
187 madvise(address, length, MADV_FREE_REUSE);
188 #endif
189
190 // On POSIX systems, the caller need simply read the memory to recommit it.
191 // This has the correct behavior because the API requires the permissions to
192 // be the same as before decommitting and all configurations can read.
193 return true;
194 }
195
DiscardSystemPagesInternal(void * address,size_t length)196 void DiscardSystemPagesInternal(void* address, size_t length) {
197 #if defined(OS_APPLE)
198 int ret = madvise(address, length, MADV_FREE_REUSABLE);
199 if (ret) {
200 // MADV_FREE_REUSABLE sometimes fails, so fall back to MADV_DONTNEED.
201 ret = madvise(address, length, MADV_DONTNEED);
202 }
203 CHECK(0 == ret);
204 #else
205 // We have experimented with other flags, but with suboptimal results.
206 //
207 // MADV_FREE (Linux): Makes our memory measurements less predictable;
208 // performance benefits unclear.
209 //
210 // Therefore, we just do the simple thing: MADV_DONTNEED.
211 CHECK(!madvise(address, length, MADV_DONTNEED));
212 #endif
213 }
214
215 } // namespace base
216 } // namespace pdfium
217
218 #endif // THIRD_PARTY_BASE_ALLOCATOR_PARTITION_ALLOCATOR_PAGE_ALLOCATOR_INTERNALS_POSIX_H_
219