/src/php-src/ext/opcache/shared_alloc_mmap.c
Line | Count | Source |
1 | | /* |
2 | | +----------------------------------------------------------------------+ |
3 | | | Zend OPcache | |
4 | | +----------------------------------------------------------------------+ |
5 | | | Copyright © The PHP Group and Contributors. | |
6 | | +----------------------------------------------------------------------+ |
7 | | | This source file is subject to the Modified BSD License that is | |
8 | | | bundled with this package in the file LICENSE, and is available | |
9 | | | through the World Wide Web at <https://www.php.net/license/>. | |
10 | | | | |
11 | | | SPDX-License-Identifier: BSD-3-Clause | |
12 | | +----------------------------------------------------------------------+ |
13 | | | Authors: Andi Gutmans <andi@php.net> | |
14 | | | Zeev Suraski <zeev@php.net> | |
15 | | | Stanislav Malyshev <stas@zend.com> | |
16 | | | Dmitry Stogov <dmitry@php.net> | |
17 | | +----------------------------------------------------------------------+ |
18 | | */ |
19 | | |
20 | | #include "zend_shared_alloc.h" |
21 | | #ifdef HAVE_JIT |
22 | | # include "jit/zend_jit.h" |
23 | | #endif |
24 | | |
25 | | #ifdef USE_MMAP |
26 | | |
27 | | #include <sys/types.h> |
28 | | #include <sys/stat.h> |
29 | | #include <stdio.h> |
30 | | #include <stdlib.h> |
31 | | #include <sys/mman.h> |
32 | | |
33 | | #ifdef __APPLE__ |
34 | | #include <mach/vm_statistics.h> |
35 | | #endif |
36 | | |
37 | | #include "zend_execute.h" |
38 | | #ifdef HAVE_PROCCTL |
39 | | #include <sys/procctl.h> |
40 | | #endif |
41 | | |
42 | | #if defined(MAP_ANON) && !defined(MAP_ANONYMOUS) |
43 | | # define MAP_ANONYMOUS MAP_ANON |
44 | | #endif |
45 | | #if defined(MAP_ALIGNED_SUPER) |
46 | | # include <sys/types.h> |
47 | | # include <sys/sysctl.h> |
48 | | # include <sys/user.h> |
49 | | # define MAP_HUGETLB MAP_ALIGNED_SUPER |
50 | | #endif |
51 | | |
52 | | #if defined(HAVE_JIT) && (defined(__linux__) || defined(__FreeBSD__)) && (defined(__x86_64__) || defined (__aarch64__)) && !defined(__SANITIZE_ADDRESS__) |
53 | | static void *find_preferred_mmap_base(size_t requested_size) |
54 | 0 | { |
55 | 0 | size_t huge_page_size = 2 * 1024 * 1024; |
56 | 0 | uintptr_t last_free_addr = huge_page_size; |
57 | 0 | uintptr_t last_candidate = (uintptr_t)MAP_FAILED; |
58 | 0 | uintptr_t start, end, text_start = 0; |
59 | 0 | #if defined(__linux__) |
60 | 0 | FILE *f; |
61 | 0 | char buffer[MAXPATHLEN]; |
62 | |
|
63 | 0 | f = fopen("/proc/self/maps", "r"); |
64 | 0 | if (!f) { |
65 | 0 | return MAP_FAILED; |
66 | 0 | } |
67 | | |
68 | 0 | while (fgets(buffer, MAXPATHLEN, f) && sscanf(buffer, "%lx-%lx", &start, &end) == 2) { |
69 | | /* Don't place the segment directly before or after the heap segment. Due to an selinux bug, |
70 | | * a segment directly preceding or following the heap is interpreted as heap memory, which |
71 | | * will result in an execheap violation for the JIT. |
72 | | * See https://bugzilla.kernel.org/show_bug.cgi?id=218258. */ |
73 | 0 | bool heap_segment = strstr(buffer, "[heap]") != NULL; |
74 | 0 | if (heap_segment) { |
75 | 0 | uintptr_t start_base = start & ~(huge_page_size - 1); |
76 | 0 | if (last_free_addr + requested_size >= start_base) { |
77 | 0 | last_free_addr = ZEND_MM_ALIGNED_SIZE_EX(end + huge_page_size, huge_page_size); |
78 | 0 | continue; |
79 | 0 | } |
80 | 0 | } |
81 | 0 | if ((uintptr_t)execute_ex >= start) { |
82 | | /* the current segment lays before PHP .text segment or PHP .text segment itself */ |
83 | | /*Search for candidates at the end of the free segment near the .text segment |
84 | | to prevent candidates from being missed due to large hole*/ |
85 | 0 | if (last_free_addr + requested_size <= start) { |
86 | 0 | last_candidate = ZEND_MM_ALIGNED_SIZE_EX(start - requested_size, huge_page_size); |
87 | 0 | if (last_candidate + requested_size > start) { |
88 | 0 | last_candidate -= huge_page_size; |
89 | 0 | } |
90 | 0 | } |
91 | 0 | if ((uintptr_t)execute_ex < end) { |
92 | | /* the current segment is PHP .text segment itself */ |
93 | 0 | if (last_candidate != (uintptr_t)MAP_FAILED) { |
94 | 0 | if (end - last_candidate < UINT32_MAX) { |
95 | | /* we have found a big enough hole before the text segment */ |
96 | 0 | break; |
97 | 0 | } |
98 | 0 | last_candidate = (uintptr_t)MAP_FAILED; |
99 | 0 | } |
100 | 0 | text_start = start; |
101 | 0 | } |
102 | 0 | } else { |
103 | | /* the current segment lays after PHP .text segment */ |
104 | 0 | if (last_free_addr + requested_size - text_start > UINT32_MAX) { |
105 | | /* the current segment and the following segments lay too far from PHP .text segment */ |
106 | 0 | break; |
107 | 0 | } |
108 | 0 | if (last_free_addr + requested_size <= start) { |
109 | 0 | last_candidate = last_free_addr; |
110 | 0 | break; |
111 | 0 | } |
112 | 0 | } |
113 | 0 | last_free_addr = ZEND_MM_ALIGNED_SIZE_EX(end, huge_page_size); |
114 | 0 | if (heap_segment) { |
115 | 0 | last_free_addr += huge_page_size; |
116 | 0 | } |
117 | 0 | } |
118 | 0 | fclose(f); |
119 | | #elif defined(__FreeBSD__) |
120 | | size_t s = 0; |
121 | | int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_VMMAP, getpid()}; |
122 | | if (sysctl(mib, 4, NULL, &s, NULL, 0) == 0) { |
123 | | s = s * 4 / 3; |
124 | | void *addr = mmap(NULL, s, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); |
125 | | if (addr != MAP_FAILED) { |
126 | | if (sysctl(mib, 4, addr, &s, NULL, 0) == 0) { |
127 | | start = (uintptr_t)addr; |
128 | | end = start + s; |
129 | | while (start < end) { |
130 | | struct kinfo_vmentry *entry = (struct kinfo_vmentry *)start; |
131 | | size_t sz = entry->kve_structsize; |
132 | | if (sz == 0) { |
133 | | break; |
134 | | } |
135 | | uintptr_t e_start = entry->kve_start; |
136 | | uintptr_t e_end = entry->kve_end; |
137 | | if ((uintptr_t)execute_ex >= e_start) { |
138 | | /* the current segment lays before PHP .text segment or PHP .text segment itself */ |
139 | | if (last_free_addr + requested_size <= e_start) { |
140 | | last_candidate = ZEND_MM_ALIGNED_SIZE_EX(e_start - requested_size, huge_page_size); |
141 | | if (last_candidate + requested_size > e_start) { |
142 | | last_candidate -= huge_page_size; |
143 | | } |
144 | | } |
145 | | if ((uintptr_t)execute_ex < e_end) { |
146 | | /* the current segment is PHP .text segment itself */ |
147 | | if (last_candidate != (uintptr_t)MAP_FAILED) { |
148 | | if (e_end - last_candidate < UINT32_MAX) { |
149 | | /* we have found a big enough hole before the text segment */ |
150 | | break; |
151 | | } |
152 | | last_candidate = (uintptr_t)MAP_FAILED; |
153 | | } |
154 | | text_start = e_start; |
155 | | } |
156 | | } else { |
157 | | /* the current segment lays after PHP .text segment */ |
158 | | if (last_free_addr + requested_size - text_start > UINT32_MAX) { |
159 | | /* the current segment and the following segments lay too far from PHP .text segment */ |
160 | | break; |
161 | | } |
162 | | if (last_free_addr + requested_size <= e_start) { |
163 | | last_candidate = last_free_addr; |
164 | | break; |
165 | | } |
166 | | } |
167 | | last_free_addr = ZEND_MM_ALIGNED_SIZE_EX(e_end, huge_page_size); |
168 | | start += sz; |
169 | | } |
170 | | } |
171 | | munmap(addr, s); |
172 | | } |
173 | | } |
174 | | #endif |
175 | |
|
176 | 0 | return (void*)last_candidate; |
177 | 0 | } |
178 | | #endif |
179 | | |
180 | | static int create_segments(size_t requested_size, zend_shared_segment ***shared_segments_p, int *shared_segments_count, const char **error_in) |
181 | 2 | { |
182 | 2 | zend_shared_segment *shared_segment; |
183 | 2 | int flags = PROT_READ | PROT_WRITE, fd = -1; |
184 | 2 | void *p; |
185 | | #if defined(HAVE_PROCCTL) && defined(PROC_WXMAP_CTL) |
186 | | int enable_wxmap = PROC_WX_MAPPINGS_PERMIT; |
187 | | if (procctl(P_PID, getpid(), PROC_WXMAP_CTL, &enable_wxmap) == -1) { |
188 | | return ALLOC_FAILURE; |
189 | | } |
190 | | #endif |
191 | | #ifdef PROT_MPROTECT |
192 | | flags |= PROT_MPROTECT(PROT_EXEC); |
193 | | #endif |
194 | | #ifdef VM_MAKE_TAG |
195 | | /* allows tracking segments via tools such as vmmap */ |
196 | | fd = VM_MAKE_TAG(251U); |
197 | | #endif |
198 | | #ifdef PROT_MAX |
199 | | flags |= PROT_MAX(PROT_READ | PROT_WRITE | PROT_EXEC); |
200 | | #endif |
201 | 2 | #if defined(HAVE_JIT) && (defined(__linux__) || defined(__FreeBSD__)) && (defined(__x86_64__) || defined (__aarch64__)) && !defined(__SANITIZE_ADDRESS__) |
202 | 2 | void *hint; |
203 | 2 | if (JIT_G(enabled) && JIT_G(buffer_size) |
204 | 0 | && zend_jit_check_support() == SUCCESS) { |
205 | 0 | hint = find_preferred_mmap_base(requested_size); |
206 | 2 | } else { |
207 | | /* Do not use a hint if JIT is not enabled, as this profits only JIT and |
208 | | * this is potentially unsafe when the only suitable candidate is just |
209 | | * after the heap (e.g. in non-PIE builds) (GH-13775). */ |
210 | 2 | hint = MAP_FAILED; |
211 | 2 | } |
212 | 2 | if (hint != MAP_FAILED) { |
213 | 0 | # ifdef MAP_HUGETLB |
214 | 0 | size_t huge_page_size = 2 * 1024 * 1024; |
215 | 0 | if (requested_size >= huge_page_size && requested_size % huge_page_size == 0) { |
216 | 0 | p = mmap(hint, requested_size, flags, MAP_SHARED|MAP_ANONYMOUS|MAP_HUGETLB|MAP_FIXED, -1, 0); |
217 | 0 | if (p != MAP_FAILED) { |
218 | 0 | goto success; |
219 | 0 | } |
220 | 0 | } |
221 | 0 | #endif |
222 | 0 | p = mmap(hint, requested_size, flags, MAP_SHARED|MAP_ANONYMOUS|MAP_FIXED, -1, 0); |
223 | 0 | if (p != MAP_FAILED) { |
224 | 0 | goto success; |
225 | 0 | } |
226 | 0 | } |
227 | 2 | #endif |
228 | 2 | #ifdef MAP_HUGETLB |
229 | 2 | size_t huge_page_size = 2 * 1024 * 1024; |
230 | | |
231 | | /* Try to allocate huge pages first to reduce dTLB misses. |
232 | | * OSes has to be configured properly |
233 | | * on Linux |
234 | | * (e.g. https://wiki.debian.org/Hugepages#Enabling_HugeTlbPage) |
235 | | * You may verify huge page usage with the following command: |
236 | | * `grep "Huge" /proc/meminfo` |
237 | | * on FreeBSD |
238 | | * sysctl vm.pmap.pg_ps_enabled entry |
239 | | * (boot time config only, but enabled by default on most arches). |
240 | | */ |
241 | 2 | if (requested_size >= huge_page_size && requested_size % huge_page_size == 0) { |
242 | 2 | # if defined(__x86_64__) && defined(MAP_32BIT) |
243 | | /* to got HUGE PAGES in low 32-bit address we have to reserve address |
244 | | space and then remap it using MAP_HUGETLB */ |
245 | | |
246 | 2 | p = mmap(NULL, requested_size, flags, MAP_SHARED|MAP_ANONYMOUS|MAP_32BIT, fd, 0); |
247 | 2 | if (p != MAP_FAILED) { |
248 | 2 | munmap(p, requested_size); |
249 | 2 | p = (void*)(ZEND_MM_ALIGNED_SIZE_EX((ptrdiff_t)p, huge_page_size)); |
250 | 2 | p = mmap(p, requested_size, flags, MAP_SHARED|MAP_ANONYMOUS|MAP_32BIT|MAP_HUGETLB|MAP_FIXED, -1, 0); |
251 | 2 | if (p != MAP_FAILED) { |
252 | 0 | goto success; |
253 | 2 | } else { |
254 | 2 | p = mmap(NULL, requested_size, flags, MAP_SHARED|MAP_ANONYMOUS|MAP_32BIT, fd, 0); |
255 | 2 | if (p != MAP_FAILED) { |
256 | 2 | goto success; |
257 | 2 | } |
258 | 2 | } |
259 | 2 | } |
260 | 0 | # endif |
261 | 0 | p = mmap(0, requested_size, flags, MAP_SHARED|MAP_ANONYMOUS|MAP_HUGETLB, fd, 0); |
262 | 0 | if (p != MAP_FAILED) { |
263 | 0 | goto success; |
264 | 0 | } |
265 | 0 | } |
266 | | #elif defined(PREFER_MAP_32BIT) && defined(__x86_64__) && defined(MAP_32BIT) |
267 | | p = mmap(NULL, requested_size, flags, MAP_SHARED|MAP_ANONYMOUS|MAP_32BIT, fd, 0); |
268 | | if (p != MAP_FAILED) { |
269 | | goto success; |
270 | | } |
271 | | #endif |
272 | | |
273 | 0 | p = mmap(0, requested_size, flags, MAP_SHARED|MAP_ANONYMOUS, fd, 0); |
274 | 0 | if (p == MAP_FAILED) { |
275 | 0 | *error_in = "mmap"; |
276 | 0 | return ALLOC_FAILURE; |
277 | 0 | } |
278 | | |
279 | 2 | success: ZEND_ATTRIBUTE_UNUSED; |
280 | 2 | *shared_segments_count = 1; |
281 | 2 | *shared_segments_p = (zend_shared_segment **) calloc(1, sizeof(zend_shared_segment) + sizeof(void *)); |
282 | 2 | if (!*shared_segments_p) { |
283 | 0 | munmap(p, requested_size); |
284 | 0 | *error_in = "calloc"; |
285 | 0 | return ALLOC_FAILURE; |
286 | 0 | } |
287 | 2 | shared_segment = (zend_shared_segment *)((char *)(*shared_segments_p) + sizeof(void *)); |
288 | 2 | (*shared_segments_p)[0] = shared_segment; |
289 | | |
290 | 2 | shared_segment->p = p; |
291 | 2 | shared_segment->pos = 0; |
292 | 2 | shared_segment->size = requested_size; |
293 | | |
294 | 2 | return ALLOC_SUCCESS; |
295 | 2 | } |
296 | | |
297 | | static int detach_segment(zend_shared_segment *shared_segment) |
298 | 0 | { |
299 | 0 | munmap(shared_segment->p, shared_segment->size); |
300 | 0 | return 0; |
301 | 0 | } |
302 | | |
303 | | static size_t segment_type_size(void) |
304 | 4 | { |
305 | 4 | return sizeof(zend_shared_segment); |
306 | 4 | } |
307 | | |
308 | | const zend_shared_memory_handlers zend_alloc_mmap_handlers = { |
309 | | create_segments, |
310 | | detach_segment, |
311 | | segment_type_size |
312 | | }; |
313 | | |
314 | | #endif /* USE_MMAP */ |