php-internal-docs 8.4.8
Unofficial docs for php/php-src
Loading...
Searching...
No Matches
shared_alloc_win32.c
Go to the documentation of this file.
1/*
2 +----------------------------------------------------------------------+
3 | Zend OPcache |
4 +----------------------------------------------------------------------+
5 | Copyright (c) The PHP Group |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | https://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Authors: Andi Gutmans <andi@php.net> |
16 | Zeev Suraski <zeev@php.net> |
17 | Stanislav Malyshev <stas@zend.com> |
18 | Dmitry Stogov <dmitry@php.net> |
19 +----------------------------------------------------------------------+
20*/
21
22#include "php.h"
23#include "ZendAccelerator.h"
24#include "zend_shared_alloc.h"
26#include "zend_execute.h"
27#include "zend_system_id.h"
28#include "SAPI.h"
29#include "tsrm_win32.h"
30#include "win32/winutil.h"
31#include <winbase.h>
32#include <process.h>
33#include <LMCONS.H>
34
35#define ACCEL_FILEMAP_NAME "ZendOPcache.SharedMemoryArea"
36#define ACCEL_MUTEX_NAME "ZendOPcache.SharedMemoryMutex"
37#define ACCEL_EVENT_SOURCE "Zend OPcache"
38
39/* address of mapping base and address of execute_ex */
40#define ACCEL_BASE_POINTER_SIZE (2 * sizeof(void*))
41
42static HANDLE memfile = NULL, memory_mutex = NULL;
43static void *mapping_base;
44
45#define MAX_MAP_RETRIES 25
46
47static void zend_win_error_message(int type, char *msg, int err)
48{
49 HANDLE h;
50 char *ev_msgs[2];
52
53 h = RegisterEventSource(NULL, TEXT(ACCEL_EVENT_SOURCE));
54 ev_msgs[0] = msg;
55 ev_msgs[1] = buf;
56 ReportEvent(h, // event log handle
57 EVENTLOG_ERROR_TYPE, // event type
58 0, // category zero
59 err, // event identifier
60 NULL, // no user security identifier
61 2, // one substitution string
62 0, // no data
63 ev_msgs, // pointer to string array
64 NULL); // pointer to data
65 DeregisterEventSource(h);
66
68
70}
71
72static char *create_name_with_username(const char *name, size_t unique_id)
73{
74 static char newname[MAXPATHLEN + 1 + 32 + 1 + 20 + 1 + 32 + sizeof("ffffffffffffffff")-1 + 1];
75 char *p = newname;
76 p += strlcpy(newname, name, MAXPATHLEN + 1);
77 *(p++) = '@';
78 p = zend_mempcpy(p, accel_uname_id, 32);
79 *(p++) = '@';
80 p += strlcpy(p, sapi_module.name, 21);
81 *(p++) = '@';
82 p = zend_mempcpy(p, zend_system_id, 32);
83 if (unique_id) {
84 p += snprintf(p, sizeof("ffffffffffffffff"), "%zx", unique_id) + 1;
85 } else {
86 *(p++) = '\0';
87 }
88 ZEND_ASSERT(p - newname <= sizeof(newname));
89
90 return newname;
91}
92
94{
95 memory_mutex = CreateMutex(NULL, FALSE, create_name_with_username(ACCEL_MUTEX_NAME, 0));
96 if (!memory_mutex) {
97 zend_accel_error(ACCEL_LOG_FATAL, "Cannot create mutex (error %u)", GetLastError());
98 return;
99 }
100 ReleaseMutex(memory_mutex);
101}
102
104{
105 DWORD waitRes = WaitForSingleObject(memory_mutex, INFINITE);
106
107 if (waitRes == WAIT_FAILED) {
108 zend_accel_error(ACCEL_LOG_ERROR, "Cannot lock mutex");
109 }
110}
111
113{
114 ReleaseMutex(memory_mutex);
115}
116
117static int zend_shared_alloc_reattach(size_t requested_size, const char **error_in)
118{
119 int err;
120 void *wanted_mapping_base;
121 MEMORY_BASIC_INFORMATION info;
122 void *execute_ex_base;
123 int execute_ex_moved;
124
125 mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS, 0, 0, ACCEL_BASE_POINTER_SIZE, NULL);
126 if (mapping_base == NULL) {
127 err = GetLastError();
128 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to read base address", err);
129 *error_in="read mapping base";
130 return ALLOC_FAILURE;
131 }
132 wanted_mapping_base = ((void**)mapping_base)[0];
133 execute_ex_base = ((void**)mapping_base)[1];
134 UnmapViewOfFile(mapping_base);
135
136 execute_ex_moved = (void *)execute_ex != execute_ex_base;
137
138 /* Check if execute_ex is at the same address and if the requested address space is free */
139 if (execute_ex_moved ||
140 VirtualQuery(wanted_mapping_base, &info, sizeof(info)) == 0 ||
141 info.State != MEM_FREE ||
142 info.RegionSize < requested_size) {
143#if ENABLE_FILE_CACHE_FALLBACK
144 if (ZCG(accel_directives).file_cache && ZCG(accel_directives).file_cache_fallback) {
145 size_t pre_size, wanted_mb_save;
146
147 wanted_mb_save = (size_t)wanted_mapping_base;
148
149 if (execute_ex_moved) {
150 err = ERROR_INVALID_ADDRESS;
151 zend_win_error_message(ACCEL_LOG_WARNING, "Opcode handlers are unusable due to ASLR (fall-back to file cache)", err);
152 } else {
153 err = ERROR_INVALID_ADDRESS;
154 zend_win_error_message(ACCEL_LOG_WARNING, "Base address marks unusable memory region (fall-back to file cache)", err);
155 }
156
157 pre_size = ZEND_ALIGNED_SIZE(sizeof(zend_smm_shared_globals)) + ZEND_ALIGNED_SIZE(sizeof(zend_shared_segment)) + ZEND_ALIGNED_SIZE(sizeof(void *)) + ZEND_ALIGNED_SIZE(sizeof(int));
158 /* Map only part of SHM to have access to opcache shared globals */
159 mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS, 0, 0, pre_size + ZEND_ALIGNED_SIZE(sizeof(zend_accel_shared_globals)), NULL);
160 if (mapping_base == NULL) {
161 err = GetLastError();
162 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to reattach to opcache shared globals", err);
163 return ALLOC_FAILURE;
164 }
165 accel_shared_globals = (zend_accel_shared_globals *)((char *)((zend_smm_shared_globals *)mapping_base)->app_shared_globals + ((char *)mapping_base - (char *)wanted_mb_save));
166
167 return ALLOC_FALLBACK;
168 }
169#endif
170 if (execute_ex_moved) {
171 err = ERROR_INVALID_ADDRESS;
172 zend_win_error_message(ACCEL_LOG_FATAL, "Opcode handlers are unusable due to ASLR. Please setup opcache.file_cache and opcache.file_cache_fallback directives for more convenient Opcache usage", err);
173 } else {
174 err = ERROR_INVALID_ADDRESS;
175 zend_win_error_message(ACCEL_LOG_FATAL, "Base address marks unusable memory region. Please setup opcache.file_cache and opcache.file_cache_fallback directives for more convenient Opcache usage", err);
176 }
177 return ALLOC_FAILURE;
178 }
179
180 mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS|FILE_MAP_EXECUTE, 0, 0, 0, wanted_mapping_base);
181
182 if (mapping_base == NULL) {
183 err = GetLastError();
184 if (err == ERROR_INVALID_ADDRESS) {
185 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to reattach to base address", err);
186 return ALLOC_FAILURE;
187 }
188 return ALLOC_FAIL_MAPPING;
189 } else {
190 DWORD old;
191
192 if (!VirtualProtect(mapping_base, requested_size, PAGE_READWRITE, &old)) {
193 err = GetLastError();
194 zend_win_error_message(ACCEL_LOG_FATAL, "VirtualProtect() failed", err);
195 return ALLOC_FAIL_MAPPING;
196 }
197 }
198
200
202}
203
204static int create_segments(size_t requested_size, zend_shared_segment ***shared_segments_p, int *shared_segments_count, const char **error_in)
205{
206 int err = 0, ret;
207 zend_shared_segment *shared_segment;
208 int map_retries = 0;
209 void *default_mapping_base_set[] = { 0, 0 };
210 /* TODO:
211 improve fixed addresses on x64. It still makes no sense to do it as Windows addresses are virtual per se and can or should be randomized anyway
212 through Address Space Layout Radomization (ASLR). We can still let the OS do its job and be sure that each process gets the same address if
213 desired. Not done yet, @zend refused but did not remember the exact reason, pls add info here if one of you know why :)
214 */
215#if defined(_WIN64)
216 void *vista_mapping_base_set[] = { (void *) 0x0000100000000000, (void *) 0x0000200000000000, (void *) 0x0000300000000000, (void *) 0x0000700000000000, 0 };
217 DWORD size_high = (requested_size >> 32), size_low = (requested_size & 0xffffffff);
218#else
219 void *vista_mapping_base_set[] = { (void *) 0x20000000, (void *) 0x21000000, (void *) 0x30000000, (void *) 0x31000000, (void *) 0x50000000, 0 };
220 DWORD size_high = 0, size_low = requested_size;
221#endif
222 void **wanted_mapping_base = default_mapping_base_set;
223
225 /* Mapping retries: When Apache2 restarts, the parent process startup routine
226 can be called before the child process is killed. In this case, the mapping will fail
227 and we have to sleep some time (until the child releases the mapping object) and retry.*/
228 do {
229 memfile = OpenFileMapping(FILE_MAP_READ|FILE_MAP_WRITE|FILE_MAP_EXECUTE, 0, create_name_with_username(ACCEL_FILEMAP_NAME, requested_size));
230 if (memfile == NULL) {
231 err = GetLastError();
232 break;
233 }
234
235 ret = zend_shared_alloc_reattach(requested_size, error_in);
236 if (ret == ALLOC_FAIL_MAPPING) {
237 err = GetLastError();
238 /* Mapping failed, wait for mapping object to get freed and retry */
239 CloseHandle(memfile);
240 memfile = NULL;
241 if (++map_retries >= MAX_MAP_RETRIES) {
242 break;
243 }
245 Sleep(1000 * (map_retries + 1));
247 } else {
249 return ret;
250 }
251 } while (1);
252
253 if (map_retries == MAX_MAP_RETRIES) {
255 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to open file mapping", err);
256 *error_in = "OpenFileMapping";
257 return ALLOC_FAILURE;
258 }
259
260 /* creating segment here */
261 *shared_segments_count = 1;
262 *shared_segments_p = (zend_shared_segment **) calloc(1, sizeof(zend_shared_segment)+sizeof(void *));
263 if (!*shared_segments_p) {
264 err = GetLastError();
266 zend_win_error_message(ACCEL_LOG_FATAL, "calloc() failed", err);
267 *error_in = "calloc";
268 return ALLOC_FAILURE;
269 }
270 shared_segment = (zend_shared_segment *)((char *)(*shared_segments_p) + sizeof(void *));
271 (*shared_segments_p)[0] = shared_segment;
272
273 memfile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE | SEC_COMMIT, size_high, size_low,
274 create_name_with_username(ACCEL_FILEMAP_NAME, requested_size));
275 if (memfile == NULL) {
276 err = GetLastError();
278 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to create file mapping", err);
279 *error_in = "CreateFileMapping";
280 return ALLOC_FAILURE;
281 }
282
283 /* Starting from Windows Vista, heap randomization occurs which might cause our mapping base to
284 be taken (fail to map). So we try to map into one of the hard coded predefined addresses
285 in high memory. */
286 if (!ZCG(accel_directives).mmap_base || !*ZCG(accel_directives).mmap_base) {
287 wanted_mapping_base = vista_mapping_base_set;
288 } else {
289 char *s = ZCG(accel_directives).mmap_base;
290
291 /* skip leading 0x, %p assumes hexdecimal format anyway */
292 if (*s == '0' && *(s + 1) == 'x') {
293 s += 2;
294 }
295 if (sscanf(s, "%p", &default_mapping_base_set[0]) != 1) {
297 zend_win_error_message(ACCEL_LOG_FATAL, "Bad mapping address specified in opcache.mmap_base", err);
298 return ALLOC_FAILURE;
299 }
300 }
301
302 do {
303 shared_segment->p = mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS|FILE_MAP_EXECUTE, 0, 0, 0, *wanted_mapping_base);
304 if (*wanted_mapping_base == NULL) { /* Auto address (NULL) is the last option on the array */
305 break;
306 }
307 wanted_mapping_base++;
308 } while (!mapping_base);
309
310 if (mapping_base == NULL) {
311 err = GetLastError();
313 zend_win_error_message(ACCEL_LOG_FATAL, "Unable to create view for file mapping", err);
314 *error_in = "MapViewOfFile";
315 return ALLOC_FAILURE;
316 } else {
317 DWORD old;
318
319 if (!VirtualProtect(mapping_base, requested_size, PAGE_READWRITE, &old)) {
320 err = GetLastError();
321 zend_win_error_message(ACCEL_LOG_FATAL, "VirtualProtect() failed", err);
322 return ALLOC_FAILURE;
323 }
324
325 ((void**)mapping_base)[0] = mapping_base;
326 ((void**)mapping_base)[1] = (void*)execute_ex;
327 }
328
329 shared_segment->pos = ACCEL_BASE_POINTER_SIZE;
330 shared_segment->size = requested_size - ACCEL_BASE_POINTER_SIZE;
331
333
334 return ALLOC_SUCCESS;
335}
336
337static int detach_segment(zend_shared_segment *shared_segment)
338{
340 if (mapping_base) {
341 UnmapViewOfFile(mapping_base);
342 mapping_base = NULL;
343 }
344 CloseHandle(memfile);
345 memfile = NULL;
347 CloseHandle(memory_mutex);
348 memory_mutex = NULL;
349 return 0;
350}
351
352static size_t segment_type_size(void)
353{
354 return sizeof(zend_shared_segment);
355}
356
358 create_segments,
359 detach_segment,
360 segment_type_size
361};
SAPI_API sapi_module_struct sapi_module
Definition SAPI.c:65
zend_accel_shared_globals * accel_shared_globals
#define ZCG(v)
struct _zend_accel_shared_globals zend_accel_shared_globals
sscanf(string $string, string $format, mixed &... $vars)
char s[4]
Definition cdf.c:77
#define DWORD
Definition exif.c:1762
zend_ffi_type * type
Definition ffi.c:3812
char * err
Definition ffi.c:3029
zend_ffi_ctype_name_buf buf
Definition ffi.c:4685
#define FALSE
Definition gd_gd.c:8
#define NULL
Definition gdcache.h:45
#define strlcpy
Definition php.h:159
char * msg
Definition phpdbg.h:289
p
Definition session.c:1105
const zend_shared_memory_handlers zend_alloc_win32_handlers
#define MAX_MAP_RETRIES
#define ACCEL_EVENT_SOURCE
void zend_shared_alloc_lock_win32(void)
void zend_shared_alloc_unlock_win32(void)
#define ACCEL_MUTEX_NAME
#define ACCEL_BASE_POINTER_SIZE
void zend_shared_alloc_create_lock(void)
#define ACCEL_FILEMAP_NAME
PHP_WINUTIL_API char * php_win32_error_to_msg(HRESULT error)
Definition winutil.c:25
PHP_WINUTIL_API void php_win32_error_msg_free(char *msg)
Definition winutil.c:50
void zend_accel_error(int type, const char *format,...)
#define ACCEL_LOG_FATAL
#define ACCEL_LOG_WARNING
#define ACCEL_LOG_ERROR
#define snprintf
ZEND_API void execute_ex(zend_execute_data *execute_data)
#define ZEND_ASSERT(c)
ZEND_EXT_API zend_smm_shared_globals * smm_shared_globals
struct _zend_shared_segment zend_shared_segment
#define ALLOC_SUCCESS
#define ALLOC_FAILURE
#define ALLOC_FALLBACK
#define ZEND_ALIGNED_SIZE(size)
#define SUCCESSFULLY_REATTACHED
#define ALLOC_FAIL_MAPPING
struct _zend_smm_shared_globals zend_smm_shared_globals
ZEND_API char zend_system_id[32]
#define MAXPATHLEN
zend_string * name
zval * ret