xref: /PHP-5.6/ext/opcache/shared_alloc_win32.c (revision 49493a2d)
1 /*
2    +----------------------------------------------------------------------+
3    | Zend OPcache                                                         |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1998-2016 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    | http://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@zend.com>                                |
16    |          Zeev Suraski <zeev@zend.com>                                |
17    |          Stanislav Malyshev <stas@zend.com>                          |
18    |          Dmitry Stogov <dmitry@zend.com>                             |
19    +----------------------------------------------------------------------+
20 */
21 
22 #include "ZendAccelerator.h"
23 #include "zend_shared_alloc.h"
24 #include "zend_accelerator_util_funcs.h"
25 #include <winbase.h>
26 #include <process.h>
27 #include <LMCONS.H>
28 
29 #include "main/php.h"
30 #include "ext/standard/md5.h"
31 
32 #define ACCEL_FILEMAP_NAME "ZendOPcache.SharedMemoryArea"
33 #define ACCEL_MUTEX_NAME "ZendOPcache.SharedMemoryMutex"
34 #define ACCEL_FILEMAP_BASE_DEFAULT 0x01000000
35 #define ACCEL_FILEMAP_BASE "ZendOPcache.MemoryBase"
36 #define ACCEL_EVENT_SOURCE "Zend OPcache"
37 
38 static HANDLE memfile = NULL, memory_mutex = NULL;
39 static void *mapping_base;
40 
41 #define MAX_MAP_RETRIES 25
42 
zend_win_error_message(int type,char * msg,int err)43 static void zend_win_error_message(int type, char *msg, int err)
44 {
45 	LPVOID lpMsgBuf;
46 	HANDLE h;
47 	char *ev_msgs[2];
48 
49 	FormatMessage(
50 		FORMAT_MESSAGE_ALLOCATE_BUFFER |
51 		FORMAT_MESSAGE_FROM_SYSTEM |
52 		FORMAT_MESSAGE_IGNORE_INSERTS,
53 		NULL,
54 		err,
55 		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
56 		(LPTSTR) &lpMsgBuf,
57 		0,
58 		NULL
59 	);
60 
61 	h = RegisterEventSource(NULL, TEXT(ACCEL_EVENT_SOURCE));
62 	ev_msgs[0] = msg;
63 	ev_msgs[1] = lpMsgBuf;
64 	ReportEvent(h,				  // event log handle
65             EVENTLOG_ERROR_TYPE,  // event type
66             0,                    // category zero
67             err,				  // event identifier
68             NULL,                 // no user security identifier
69             2,                    // one substitution string
70             0,                    // no data
71             ev_msgs,              // pointer to string array
72             NULL);                // pointer to data
73 	DeregisterEventSource(h);
74 
75 	LocalFree( lpMsgBuf );
76 
77 	zend_accel_error(type, msg);
78 }
79 
80 /* Backported from 7.0, the way no globals are touched which means win32
81    only where it's essentially needed. */
82 #define ZEND_BIN_ID "BIN_" ZEND_TOSTR(SIZEOF_CHAR) ZEND_TOSTR(SIZEOF_INT) ZEND_TOSTR(SIZEOF_LONG) ZEND_TOSTR(SIZEOF_SIZE_T) ZEND_TOSTR(SIZEOF_ZEND_LONG) ZEND_TOSTR(ZEND_MM_ALIGNMENT)
accel_gen_system_id(void)83 static char *accel_gen_system_id(void)
84 {
85 	PHP_MD5_CTX context;
86 	unsigned char digest[16], c;
87 	int i;
88 	static char md5str[32];
89 	static zend_bool done = 0;
90 
91 	if (done) {
92 		return md5str;
93 	}
94 
95 	PHP_MD5Init(&context);
96 	PHP_MD5Update(&context, PHP_VERSION, sizeof(PHP_VERSION)-1);
97 	PHP_MD5Update(&context, ZEND_EXTENSION_BUILD_ID, sizeof(ZEND_EXTENSION_BUILD_ID)-1);
98 	PHP_MD5Update(&context, ZEND_BIN_ID, sizeof(ZEND_BIN_ID)-1);
99 	if (strstr(PHP_VERSION, "-dev") != 0) {
100 		/* Development versions may be changed from build to build */
101 		PHP_MD5Update(&context, __DATE__, sizeof(__DATE__)-1);
102 		PHP_MD5Update(&context, __TIME__, sizeof(__TIME__)-1);
103 	}
104 	PHP_MD5Final(digest, &context);
105 	for (i = 0; i < 16; i++) {
106 		c = digest[i] >> 4;
107 		c = (c <= 9) ? c + '0' : c - 10 + 'a';
108 		md5str[i * 2] = c;
109 		c = digest[i] &  0x0f;
110 		c = (c <= 9) ? c + '0' : c - 10 + 'a';
111 		md5str[(i * 2) + 1] = c;
112 	}
113 
114 	done = 1;
115 
116 	return md5str;
117 }
118 
create_name_with_username(char * name)119 static char *create_name_with_username(char *name)
120 {
121 	static char newname[MAXPATHLEN + UNLEN + 4 + 1 + 32];
122 	char uname[UNLEN + 1];
123 	DWORD unsize = UNLEN;
124 
125 	GetUserName(uname, &unsize);
126 	snprintf(newname, sizeof(newname) - 1, "%s@%s@%.32s", name, uname, accel_gen_system_id());
127 	return newname;
128 }
129 
get_mmap_base_file(void)130 static char *get_mmap_base_file(void)
131 {
132 	static char windir[MAXPATHLEN+UNLEN + 3 + sizeof("\\\\@") + 1 + 32];
133 	char uname[UNLEN + 1];
134 	DWORD unsize = UNLEN;
135 	int l;
136 
137 	GetTempPath(MAXPATHLEN, windir);
138 	GetUserName(uname, &unsize);
139 	l = strlen(windir);
140 	snprintf(windir + l, sizeof(windir) - l - 1, "\\%s@%s@%.32s", ACCEL_FILEMAP_BASE, uname, accel_gen_system_id());
141 	return windir;
142 }
143 
zend_shared_alloc_create_lock(void)144 void zend_shared_alloc_create_lock(void)
145 {
146 	memory_mutex = CreateMutex(NULL, FALSE, create_name_with_username(ACCEL_MUTEX_NAME));
147 	if (!memory_mutex) {
148 		zend_accel_error(ACCEL_LOG_FATAL, "Cannot create mutex");
149 		return;
150 	}
151 	ReleaseMutex(memory_mutex);
152 }
153 
zend_shared_alloc_lock_win32(void)154 void zend_shared_alloc_lock_win32(void)
155 {
156 	DWORD waitRes = WaitForSingleObject(memory_mutex, INFINITE);
157 
158 	if (waitRes == WAIT_FAILED) {
159 		zend_accel_error(ACCEL_LOG_ERROR, "Cannot lock mutex");
160 	}
161 }
162 
zend_shared_alloc_unlock_win32(void)163 void zend_shared_alloc_unlock_win32(void)
164 {
165 	ReleaseMutex(memory_mutex);
166 }
167 
zend_shared_alloc_reattach(size_t requested_size,char ** error_in)168 static int zend_shared_alloc_reattach(size_t requested_size, char **error_in)
169 {
170 	int err;
171 	void *wanted_mapping_base;
172 	char *mmap_base_file = get_mmap_base_file();
173 	FILE *fp = fopen(mmap_base_file, "r");
174 	MEMORY_BASIC_INFORMATION info;
175 
176 	err = GetLastError();
177 	if (!fp) {
178 		zend_win_error_message(ACCEL_LOG_WARNING, mmap_base_file, err);
179 		zend_win_error_message(ACCEL_LOG_FATAL, "Unable to open base address file", err);
180 		*error_in="fopen";
181 		return ALLOC_FAILURE;
182 	}
183 	if (!fscanf(fp, "%p", &wanted_mapping_base)) {
184 		err = GetLastError();
185 		zend_win_error_message(ACCEL_LOG_FATAL, "Unable to read base address", err);
186 		*error_in="read mapping base";
187 		fclose(fp);
188 		return ALLOC_FAILURE;
189 	}
190 	fclose(fp);
191 
192 	/* Check if the requested address space is free */
193 	if (VirtualQuery(wanted_mapping_base, &info, sizeof(info)) == 0 ||
194 	    info.State != MEM_FREE ||
195 	    info.RegionSize < requested_size) {
196 	    err = ERROR_INVALID_ADDRESS;
197 		zend_win_error_message(ACCEL_LOG_FATAL, "Unable to reattach to base address", err);
198 		return ALLOC_FAILURE;
199    	}
200 
201 	mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS, 0, 0, 0, wanted_mapping_base);
202 	err = GetLastError();
203 
204 	if (mapping_base == NULL) {
205 		if (err == ERROR_INVALID_ADDRESS) {
206 			zend_win_error_message(ACCEL_LOG_FATAL, "Unable to reattach to base address", err);
207 			return ALLOC_FAILURE;
208 		}
209 		return ALLOC_FAIL_MAPPING;
210 	}
211 	smm_shared_globals = (zend_smm_shared_globals *) mapping_base;
212 
213 	return SUCCESSFULLY_REATTACHED;
214 }
215 
create_segments(size_t requested_size,zend_shared_segment *** shared_segments_p,int * shared_segments_count,char ** error_in)216 static int create_segments(size_t requested_size, zend_shared_segment ***shared_segments_p, int *shared_segments_count, char **error_in)
217 {
218 	int err, ret;
219 	zend_shared_segment *shared_segment;
220 	int map_retries = 0;
221 	void *default_mapping_base_set[] = { 0, 0 };
222 	/* TODO:
223 	  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
224 	  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
225 	  desired. Not done yet, @zend refused but did not remember the exact reason, pls add info here if one of you know why :)
226 	*/
227 #if defined(_WIN64)
228 	void *vista_mapping_base_set[] = { (void *) 0x0000100000000000, (void *) 0x0000200000000000, (void *) 0x0000300000000000, (void *) 0x0000700000000000, 0 };
229 #else
230 	void *vista_mapping_base_set[] = { (void *) 0x20000000, (void *) 0x21000000, (void *) 0x30000000, (void *) 0x31000000, (void *) 0x50000000, 0 };
231 #endif
232 	void **wanted_mapping_base = default_mapping_base_set;
233 	TSRMLS_FETCH();
234 
235 	zend_shared_alloc_lock_win32();
236 	/* Mapping retries: When Apache2 restarts, the parent process startup routine
237 	   can be called before the child process is killed. In this case, the map will fail
238 	   and we have to sleep some time (until the child releases the mapping object) and retry.*/
239 	do {
240 		memfile = OpenFileMapping(FILE_MAP_WRITE, 0, create_name_with_username(ACCEL_FILEMAP_NAME));
241 		err = GetLastError();
242 		if (memfile == NULL) {
243 			break;
244 		}
245 
246 		ret =  zend_shared_alloc_reattach(requested_size, error_in);
247 		err = GetLastError();
248 		if (ret == ALLOC_FAIL_MAPPING) {
249 			/* Mapping failed, wait for mapping object to get freed and retry */
250 			CloseHandle(memfile);
251 			memfile = NULL;
252 			if (++map_retries >= MAX_MAP_RETRIES) {
253 				break;
254 			}
255 			zend_shared_alloc_unlock_win32();
256 			Sleep(1000 * (map_retries + 1));
257 			zend_shared_alloc_lock_win32();
258 		} else {
259 			zend_shared_alloc_unlock_win32();
260 			return ret;
261 		}
262 	} while (1);
263 
264 	if (map_retries == MAX_MAP_RETRIES) {
265 		zend_shared_alloc_unlock_win32();
266 		zend_win_error_message(ACCEL_LOG_FATAL, "Unable to open file mapping", err);
267 		*error_in = "OpenFileMapping";
268 		return ALLOC_FAILURE;
269 	}
270 
271 	/* creating segment here */
272 	*shared_segments_count = 1;
273 	*shared_segments_p = (zend_shared_segment **) calloc(1, sizeof(zend_shared_segment)+sizeof(void *));
274 	if (!*shared_segments_p) {
275 		zend_shared_alloc_unlock_win32();
276 		zend_win_error_message(ACCEL_LOG_FATAL, "calloc() failed", GetLastError());
277 		*error_in = "calloc";
278 		return ALLOC_FAILURE;
279 	}
280 	shared_segment = (zend_shared_segment *)((char *)(*shared_segments_p) + sizeof(void *));
281 	(*shared_segments_p)[0] = shared_segment;
282 
283 	memfile	= CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, requested_size,
284 								create_name_with_username(ACCEL_FILEMAP_NAME));
285 	err = GetLastError();
286 	if (memfile == NULL) {
287 		zend_shared_alloc_unlock_win32();
288 		zend_win_error_message(ACCEL_LOG_FATAL, "Unable to create file mapping", err);
289 		*error_in = "CreateFileMapping";
290 		return ALLOC_FAILURE;
291 	}
292 
293 	/* Starting from windows Vista, heap randomization occurs which might cause our mapping base to
294 	   be taken (fail to map). So under Vista, we try to map into a hard coded predefined addresses
295 	   in high memory. */
296 	if (!ZCG(accel_directives).mmap_base || !*ZCG(accel_directives).mmap_base) {
297 		do {
298 			OSVERSIONINFOEX osvi;
299 			SYSTEM_INFO si;
300 
301 			ZeroMemory(&si, sizeof(SYSTEM_INFO));
302 			ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
303 
304 			osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
305 
306 			if (! GetVersionEx ((OSVERSIONINFO *) &osvi)) {
307 				osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
308 				if (!GetVersionEx((OSVERSIONINFO *)&osvi)) {
309 					break;
310 				}
311 			}
312 
313 			GetSystemInfo(&si);
314 
315 			/* Are we running Vista ? */
316 			if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT && osvi.dwMajorVersion >= 6) {
317 				wanted_mapping_base = vista_mapping_base_set;
318 			}
319 		} while (0);
320 	} else {
321 		char *s = ZCG(accel_directives).mmap_base;
322 
323 		/* skip leading 0x, %p assumes hexdeciaml format anyway */
324 		if (*s == '0' && *(s + 1) == 'x') {
325 			s += 2;
326 		}
327 		if (sscanf(s, "%p", &default_mapping_base_set[0]) != 1) {
328 			zend_shared_alloc_unlock_win32();
329 			zend_win_error_message(ACCEL_LOG_FATAL, "Bad mapping address specified in opcache.mmap_base", err);
330 			return ALLOC_FAILURE;
331 		}
332 	}
333 
334 	do {
335 		shared_segment->p = mapping_base = MapViewOfFileEx(memfile, FILE_MAP_ALL_ACCESS, 0, 0, 0, *wanted_mapping_base);
336 		if (*wanted_mapping_base == NULL) { /* Auto address (NULL) is the last option on the array */
337 			break;
338 		}
339 		wanted_mapping_base++;
340 	} while (!mapping_base);
341 
342 	err = GetLastError();
343 	if (mapping_base == NULL) {
344 		zend_shared_alloc_unlock_win32();
345 		zend_win_error_message(ACCEL_LOG_FATAL, "Unable to create view for file mapping", err);
346 		*error_in = "MapViewOfFile";
347 		return ALLOC_FAILURE;
348 	} else {
349 		char *mmap_base_file = get_mmap_base_file();
350 		FILE *fp = fopen(mmap_base_file, "w");
351 		err = GetLastError();
352 		if (!fp) {
353 			zend_shared_alloc_unlock_win32();
354 			zend_win_error_message(ACCEL_LOG_WARNING, mmap_base_file, err);
355 			zend_win_error_message(ACCEL_LOG_FATAL, "Unable to write base address", err);
356 			return ALLOC_FAILURE;
357 		}
358 		fprintf(fp, "%p\n", mapping_base);
359 		fclose(fp);
360 	}
361 
362 	shared_segment->pos = 0;
363 	shared_segment->size = requested_size;
364 
365 	zend_shared_alloc_unlock_win32();
366 
367 	return ALLOC_SUCCESS;
368 }
369 
detach_segment(zend_shared_segment * shared_segment)370 static int detach_segment(zend_shared_segment *shared_segment)
371 {
372 	zend_shared_alloc_lock_win32();
373 	if (mapping_base) {
374 		UnmapViewOfFile(mapping_base);
375 	}
376 	CloseHandle(memfile);
377 	zend_shared_alloc_unlock_win32();
378 	CloseHandle(memory_mutex);
379 	return 0;
380 }
381 
segment_type_size(void)382 static size_t segment_type_size(void)
383 {
384 	return sizeof(zend_shared_segment);
385 }
386 
387 zend_shared_memory_handlers zend_alloc_win32_handlers = {
388 	create_segments,
389 	detach_segment,
390 	segment_type_size
391 };
392