xref: /PHP-5.6/sapi/phpdbg/phpdbg_watch.c (revision 49493a2d)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 5                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-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: Felipe Pena <felipe@php.net>                                |
16    | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
17    | Authors: Bob Weinand <bwoebi@php.net>                                |
18    +----------------------------------------------------------------------+
19 */
20 
21 #include "zend.h"
22 #include "phpdbg.h"
23 #include "phpdbg_btree.h"
24 #include "phpdbg_watch.h"
25 #include "phpdbg_utils.h"
26 #ifndef _WIN32
27 # include <unistd.h>
28 # include <sys/mman.h>
29 #endif
30 
31 ZEND_EXTERN_MODULE_GLOBALS(phpdbg);
32 
33 
34 typedef struct {
35 	void *page;
36 	size_t size;
37 	char reenable_writing;
38 	/* data must be last element */
39 	void *data;
40 } phpdbg_watch_memdump;
41 
42 #define MEMDUMP_SIZE(size) (sizeof(phpdbg_watch_memdump) - sizeof(void *) + (size))
43 
44 
phpdbg_check_for_watchpoint(void * addr TSRMLS_DC)45 static phpdbg_watchpoint_t *phpdbg_check_for_watchpoint(void *addr TSRMLS_DC) {
46 	phpdbg_watchpoint_t *watch;
47 	phpdbg_btree_result *result = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), (zend_ulong)phpdbg_get_page_boundary(addr) + phpdbg_pagesize - 1);
48 
49 	if (result == NULL) {
50 		return NULL;
51 	}
52 
53 	watch = result->ptr;
54 
55 	/* check if that addr is in a mprotect()'ed memory area */
56 	if ((char *)phpdbg_get_page_boundary(watch->addr.ptr) > (char *)addr || (char *)phpdbg_get_page_boundary(watch->addr.ptr) + phpdbg_get_total_page_size(watch->addr.ptr, watch->size) < (char *)addr) {
57 		/* failure */
58 		return NULL;
59 	}
60 
61 	return watch;
62 }
63 
phpdbg_change_watchpoint_access(phpdbg_watchpoint_t * watch,int access TSRMLS_DC)64 static void phpdbg_change_watchpoint_access(phpdbg_watchpoint_t *watch, int access TSRMLS_DC) {
65 	int m;
66 
67 	/* pagesize is assumed to be in the range of 2^x */
68 	m = mprotect(phpdbg_get_page_boundary(watch->addr.ptr), phpdbg_get_total_page_size(watch->addr.ptr, watch->size), access);
69 }
70 
phpdbg_activate_watchpoint(phpdbg_watchpoint_t * watch TSRMLS_DC)71 static inline void phpdbg_activate_watchpoint(phpdbg_watchpoint_t *watch TSRMLS_DC) {
72 	phpdbg_change_watchpoint_access(watch, PROT_READ TSRMLS_CC);
73 }
74 
phpdbg_deactivate_watchpoint(phpdbg_watchpoint_t * watch TSRMLS_DC)75 static inline void phpdbg_deactivate_watchpoint(phpdbg_watchpoint_t *watch TSRMLS_DC) {
76 	phpdbg_change_watchpoint_access(watch, PROT_READ | PROT_WRITE TSRMLS_CC);
77 }
78 
phpdbg_store_watchpoint(phpdbg_watchpoint_t * watch TSRMLS_DC)79 static inline void phpdbg_store_watchpoint(phpdbg_watchpoint_t *watch TSRMLS_DC) {
80 	phpdbg_btree_insert(&PHPDBG_G(watchpoint_tree), (zend_ulong)watch->addr.ptr, watch);
81 }
82 
phpdbg_remove_watchpoint(phpdbg_watchpoint_t * watch TSRMLS_DC)83 static inline void phpdbg_remove_watchpoint(phpdbg_watchpoint_t *watch TSRMLS_DC) {
84 	phpdbg_btree_delete(&PHPDBG_G(watchpoint_tree), (zend_ulong)watch->addr.ptr);
85 }
86 
phpdbg_create_addr_watchpoint(void * addr,size_t size,phpdbg_watchpoint_t * watch)87 void phpdbg_create_addr_watchpoint(void *addr, size_t size, phpdbg_watchpoint_t *watch) {
88 	watch->addr.ptr = addr;
89 	watch->size = size;
90 }
91 
phpdbg_create_zval_watchpoint(zval * zv,phpdbg_watchpoint_t * watch)92 void phpdbg_create_zval_watchpoint(zval *zv, phpdbg_watchpoint_t *watch) {
93 	phpdbg_create_addr_watchpoint(zv, sizeof(zval), watch);
94 	watch->type = WATCH_ON_ZVAL;
95 }
96 
phpdbg_create_ht_watchpoint(HashTable * ht,phpdbg_watchpoint_t * watch)97 void phpdbg_create_ht_watchpoint(HashTable *ht, phpdbg_watchpoint_t *watch) {
98 	phpdbg_create_addr_watchpoint(ht, sizeof(HashTable), watch);
99 	watch->type = WATCH_ON_HASHTABLE;
100 }
101 
102 void phpdbg_watch_HashTable_dtor(zval **ptr);
103 
phpdbg_create_watchpoint(phpdbg_watchpoint_t * watch TSRMLS_DC)104 static int phpdbg_create_watchpoint(phpdbg_watchpoint_t *watch TSRMLS_DC) {
105 	watch->flags |= PHPDBG_WATCH_SIMPLE;
106 
107 	phpdbg_store_watchpoint(watch TSRMLS_CC);
108 	zend_hash_add(&PHPDBG_G(watchpoints), watch->str, watch->str_len, &watch, sizeof(phpdbg_watchpoint_t *), NULL);
109 
110 	if (watch->type == WATCH_ON_ZVAL) {
111 		phpdbg_btree_insert(&PHPDBG_G(watch_HashTables), (zend_ulong)watch->parent_container, watch->parent_container->pDestructor);
112 		watch->parent_container->pDestructor = (dtor_func_t)phpdbg_watch_HashTable_dtor;
113 	}
114 
115 	phpdbg_activate_watchpoint(watch TSRMLS_CC);
116 
117 	return SUCCESS;
118 }
119 
phpdbg_create_array_watchpoint(phpdbg_watchpoint_t * watch TSRMLS_DC)120 static int phpdbg_create_array_watchpoint(phpdbg_watchpoint_t *watch TSRMLS_DC) {
121 	HashTable *ht;
122 
123 	switch (Z_TYPE_P(watch->addr.zv)) {
124 		case IS_ARRAY:
125 			ht = Z_ARRVAL_P(watch->addr.zv);
126 			break;
127 		case IS_OBJECT:
128 			ht = Z_OBJPROP_P(watch->addr.zv);
129 			break;
130 		default:
131 			return FAILURE;
132 	}
133 
134 	phpdbg_create_ht_watchpoint(ht, watch);
135 
136 	phpdbg_create_watchpoint(watch TSRMLS_CC);
137 
138 	return SUCCESS;
139 }
140 
phpdbg_get_property_key(char * key)141 static char *phpdbg_get_property_key(char *key) {
142 	if (*key != 0) {
143 		return key;
144 	}
145 	return strchr(key + 1, 0) + 1;
146 }
147 
phpdbg_create_recursive_watchpoint(phpdbg_watchpoint_t * watch TSRMLS_DC)148 static int phpdbg_create_recursive_watchpoint(phpdbg_watchpoint_t *watch TSRMLS_DC) {
149 	HashTable *ht;
150 
151 	if (watch->type != WATCH_ON_ZVAL) {
152 		return FAILURE;
153 	}
154 
155 	watch->flags |= PHPDBG_WATCH_RECURSIVE;
156 	phpdbg_create_watchpoint(watch TSRMLS_CC);
157 
158 	switch (Z_TYPE_P(watch->addr.zv)) {
159 		case IS_ARRAY:
160 			ht = Z_ARRVAL_P(watch->addr.zv);
161 			break;
162 		case IS_OBJECT:
163 			ht = Z_OBJPROP_P(watch->addr.zv);
164 			break;
165 		default:
166 			return SUCCESS;
167 	}
168 
169 	{
170 		HashPosition position;
171 		zval **zv;
172 		zval key;
173 
174 		for (zend_hash_internal_pointer_reset_ex(ht, &position);
175 		     zend_hash_get_current_data_ex(ht, (void **)&zv, &position) == SUCCESS;
176 		     zend_hash_move_forward_ex(ht, &position)) {
177 			phpdbg_watchpoint_t *new_watch = emalloc(sizeof(phpdbg_watchpoint_t));
178 
179 			new_watch->flags = PHPDBG_WATCH_RECURSIVE;
180 			new_watch->parent = watch;
181 			new_watch->parent_container = ht;
182 
183 			zend_hash_get_current_key_zval_ex(ht, &key, &position);
184 			if (Z_TYPE(key) == IS_STRING) {
185 				new_watch->name_in_parent = zend_strndup(Z_STRVAL(key), Z_STRLEN(key));
186 				new_watch->name_in_parent_len = Z_STRLEN(key);
187 			} else {
188 				new_watch->name_in_parent = NULL;
189 				new_watch->name_in_parent_len = asprintf(&new_watch->name_in_parent, "%ld", Z_LVAL(key));
190 			}
191 
192 			new_watch->str = NULL;
193 			new_watch->str_len = asprintf(&new_watch->str, "%.*s%s%s%s", (int)watch->str_len, watch->str, Z_TYPE_P(watch->addr.zv) == IS_ARRAY?"[":"->", phpdbg_get_property_key(new_watch->name_in_parent), Z_TYPE_P(watch->addr.zv) == IS_ARRAY?"]":"");
194 
195 			phpdbg_create_zval_watchpoint(*zv, new_watch);
196 			phpdbg_create_recursive_watchpoint(new_watch TSRMLS_CC);
197 		}
198 	}
199 
200 	{
201 		phpdbg_watchpoint_t *new_watch = emalloc(sizeof(phpdbg_watchpoint_t));
202 
203 		new_watch->parent = watch;
204 		new_watch->parent_container = watch->parent_container;
205 		new_watch->name_in_parent = zend_strndup(watch->name_in_parent, watch->name_in_parent_len);
206 		new_watch->name_in_parent_len = watch->name_in_parent_len;
207 		new_watch->str = NULL;
208 		new_watch->str_len = asprintf(&new_watch->str, "%.*s[]", (int)watch->str_len, watch->str);
209 		new_watch->flags = PHPDBG_WATCH_RECURSIVE;
210 
211 		phpdbg_create_ht_watchpoint(ht, new_watch);
212 		phpdbg_create_watchpoint(new_watch TSRMLS_CC);
213 	}
214 
215 	return SUCCESS;
216 }
217 
phpdbg_delete_watchpoint_recursive(phpdbg_watchpoint_t * watch,zend_bool user_request TSRMLS_DC)218 static int phpdbg_delete_watchpoint_recursive(phpdbg_watchpoint_t *watch, zend_bool user_request TSRMLS_DC) {
219 	if (watch->type == WATCH_ON_HASHTABLE || (watch->type == WATCH_ON_ZVAL && (Z_TYPE_P(watch->addr.zv) == IS_ARRAY || Z_TYPE_P(watch->addr.zv) == IS_OBJECT))) {
220 		HashTable *ht;
221 		phpdbg_btree_result *result;
222 
223 		if (watch->type == WATCH_ON_HASHTABLE && user_request) {
224 			HashPosition position;
225 			zval **zv;
226 			zval key;
227 			char *str;
228 			int str_len;
229 			phpdbg_watchpoint_t **watchpoint;
230 
231 			ht = watch->addr.ht;
232 
233 			for (zend_hash_internal_pointer_reset_ex(ht, &position);
234 			     zend_hash_get_current_data_ex(ht, (void **)&zv, &position) == SUCCESS;
235 			     zend_hash_move_forward_ex(ht, &position)) {
236 				zend_hash_get_current_key_zval_ex(ht, &key, &position);
237 				str = NULL;
238 				if (Z_TYPE(key) == IS_STRING) {
239 					str_len = asprintf(&str, "%.*s%s%s%s", (int)watch->parent->str_len, watch->parent->str, Z_TYPE_P(watch->parent->addr.zv) == IS_ARRAY?"[":"->", phpdbg_get_property_key(Z_STRVAL(key)), Z_TYPE_P(watch->parent->addr.zv) == IS_ARRAY?"]":"");
240 				} else {
241 					str_len = asprintf(&str, "%.*s%s%li%s", (int)watch->parent->str_len, watch->parent->str, Z_TYPE_P(watch->parent->addr.zv) == IS_ARRAY?"[":"->", Z_LVAL(key), Z_TYPE_P(watch->parent->addr.zv) == IS_ARRAY?"]":"");
242 				}
243 
244 				if (zend_hash_find(&PHPDBG_G(watchpoints), str, str_len, (void **) &watchpoint) == SUCCESS) {
245 					phpdbg_delete_watchpoint_recursive(*watchpoint, 1 TSRMLS_CC);
246 				}
247 			}
248 		} else {
249 			switch (Z_TYPE_P(watch->addr.zv)) {
250 				case IS_ARRAY:
251 					ht = Z_ARRVAL_P(watch->addr.zv);
252 					break;
253 				case IS_OBJECT:
254 					ht = Z_OBJPROP_P(watch->addr.zv);
255 					break;
256 			}
257 
258 			if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) ht))) {
259 				phpdbg_delete_watchpoint_recursive((phpdbg_watchpoint_t *) result->ptr, user_request TSRMLS_CC);
260 			}
261 		}
262 	}
263 
264 	return zend_hash_del(&PHPDBG_G(watchpoints), watch->str, watch->str_len);
265 }
266 
phpdbg_delete_watchpoint(phpdbg_watchpoint_t * tmp_watch TSRMLS_DC)267 static int phpdbg_delete_watchpoint(phpdbg_watchpoint_t *tmp_watch TSRMLS_DC) {
268 	int ret;
269 	phpdbg_watchpoint_t *watch;
270 	phpdbg_btree_result *result;
271 
272 	if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong)tmp_watch->addr.ptr)) == NULL) {
273 		return FAILURE;
274 	}
275 
276 	watch = result->ptr;
277 
278 	if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
279 		ret = phpdbg_delete_watchpoint_recursive(watch, 1 TSRMLS_CC);
280 	} else {
281 		ret = zend_hash_del(&PHPDBG_G(watchpoints), watch->str, watch->str_len);
282 	}
283 
284 	free(tmp_watch->str);
285 	efree(tmp_watch);
286 
287 	return ret;
288 }
289 
phpdbg_watchpoint_parse_input(char * input,size_t len,HashTable * parent,size_t i,int (* callback)(phpdbg_watchpoint_t * TSRMLS_DC),zend_bool silent TSRMLS_DC)290 static int phpdbg_watchpoint_parse_input(char *input, size_t len, HashTable *parent, size_t i, int (*callback)(phpdbg_watchpoint_t * TSRMLS_DC), zend_bool silent TSRMLS_DC) {
291 	int ret = FAILURE;
292 	zend_bool new_index = 1;
293 	char *last_index;
294 	int index_len = 0;
295 	zval **zv;
296 
297 	if (len < 2 || *input != '$') {
298 		goto error;
299 	}
300 
301 	while (i++ < len) {
302 		if (i == len) {
303 			new_index = 1;
304 		} else {
305 			switch (input[i]) {
306 				case '[':
307 					new_index = 1;
308 					break;
309 				case ']':
310 					break;
311 				case '>':
312 					if (last_index[index_len - 1] == '-') {
313 						new_index = 1;
314 						index_len--;
315 					}
316 					break;
317 
318 				default:
319 					if (new_index) {
320 						last_index = input + i;
321 						new_index = 0;
322 					}
323 					if (input[i - 1] == ']') {
324 						goto error;
325 					}
326 					index_len++;
327 			}
328 		}
329 
330 		if (new_index && index_len == 0) {
331 			HashPosition position;
332 			for (zend_hash_internal_pointer_reset_ex(parent, &position);
333 			     zend_hash_get_current_data_ex(parent, (void **)&zv, &position) == SUCCESS;
334 			     zend_hash_move_forward_ex(parent, &position)) {
335 				if (i == len || (i == len - 1 && input[len - 1] == ']')) {
336 					zval *key = emalloc(sizeof(zval));
337 					phpdbg_watchpoint_t *watch = emalloc(sizeof(phpdbg_watchpoint_t));
338 					watch->flags = 0;
339 					zend_hash_get_current_key_zval_ex(parent, key, &position);
340 					convert_to_string(key);
341 					watch->str = malloc(i + Z_STRLEN_P(key) + 2);
342 					watch->str_len = sprintf(watch->str, "%.*s%s%s", (int)i, input, phpdbg_get_property_key(Z_STRVAL_P(key)), input[len - 1] == ']'?"]":"");
343 					efree(key);
344 					watch->name_in_parent = zend_strndup(last_index, index_len);
345 					watch->name_in_parent_len = index_len;
346 					watch->parent_container = parent;
347 					phpdbg_create_zval_watchpoint(*zv, watch);
348 
349 					ret = callback(watch TSRMLS_CC) == SUCCESS || ret == SUCCESS?SUCCESS:FAILURE;
350 				} else if (Z_TYPE_PP(zv) == IS_OBJECT) {
351 					phpdbg_watchpoint_parse_input(input, len, Z_OBJPROP_PP(zv), i, callback, silent TSRMLS_CC);
352 				} else if (Z_TYPE_PP(zv) == IS_ARRAY) {
353 					phpdbg_watchpoint_parse_input(input, len, Z_ARRVAL_PP(zv), i, callback, silent TSRMLS_CC);
354 				} else {
355 					/* Ignore silently */
356 				}
357 			}
358 			return ret;
359 		} else if (new_index) {
360 			char last_chr = last_index[index_len];
361 			last_index[index_len] = 0;
362 			if (zend_symtable_find(parent, last_index, index_len + 1, (void **)&zv) == FAILURE) {
363 				if (!silent) {
364 					phpdbg_error("%.*s is undefined", (int)i, input);
365 				}
366 				return FAILURE;
367 			}
368 			last_index[index_len] = last_chr;
369 			if (i == len) {
370 				phpdbg_watchpoint_t *watch = emalloc(sizeof(phpdbg_watchpoint_t));
371 				watch->flags = 0;
372 				watch->str = zend_strndup(input, len);
373 				watch->str_len = len;
374 				watch->name_in_parent = zend_strndup(last_index, index_len);
375 				watch->name_in_parent_len = index_len;
376 				watch->parent_container = parent;
377 				phpdbg_create_zval_watchpoint(*zv, watch);
378 
379 				ret = callback(watch TSRMLS_CC) == SUCCESS || ret == SUCCESS?SUCCESS:FAILURE;
380 			} else if (Z_TYPE_PP(zv) == IS_OBJECT) {
381 				parent = Z_OBJPROP_PP(zv);
382 			} else if (Z_TYPE_PP(zv) == IS_ARRAY) {
383 				parent = Z_ARRVAL_PP(zv);
384 			} else {
385 				phpdbg_error("%.*s is nor an array nor an object", (int)i, input);
386 				return FAILURE;
387 			}
388 			index_len = 0;
389 		}
390 	}
391 
392 	return ret;
393 	error:
394 		phpdbg_error("Malformed input");
395 		return FAILURE;
396 }
397 
phpdbg_watchpoint_parse_symtables(char * input,size_t len,int (* callback)(phpdbg_watchpoint_t * TSRMLS_DC)TSRMLS_DC)398 static int phpdbg_watchpoint_parse_symtables(char *input, size_t len, int (*callback)(phpdbg_watchpoint_t * TSRMLS_DC) TSRMLS_DC) {
399 	if (EG(This) && len >= 5 && !memcmp("$this", input, 5)) {
400 		zend_hash_add(EG(active_symbol_table), "this", sizeof("this"), &EG(This), sizeof(zval *), NULL);
401 	}
402 
403 	if (zend_is_auto_global(input, len TSRMLS_CC) && phpdbg_watchpoint_parse_input(input, len, &EG(symbol_table), 0, callback, 1 TSRMLS_CC) != FAILURE) {
404 		return SUCCESS;
405 	}
406 
407 	return phpdbg_watchpoint_parse_input(input, len, EG(active_symbol_table), 0, callback, 0 TSRMLS_CC);
408 }
409 
PHPDBG_WATCH(delete)410 PHPDBG_WATCH(delete) /* {{{ */
411 {
412 	switch (param->type) {
413 		case STR_PARAM:
414 			if (phpdbg_delete_var_watchpoint(param->str, param->len TSRMLS_CC) == FAILURE) {
415 				phpdbg_error("Nothing was deleted, no corresponding watchpoint found");
416 			} else {
417 				phpdbg_notice("Removed watchpoint %.*s", (int)param->len, param->str);
418 			}
419 			break;
420 
421 		phpdbg_default_switch_case();
422 	}
423 
424 	return SUCCESS;
425 } /* }}} */
426 
PHPDBG_WATCH(recursive)427 PHPDBG_WATCH(recursive) /* {{{ */
428 {
429 	if (phpdbg_rebuild_symtable(TSRMLS_C) == FAILURE) {
430 		return SUCCESS;
431 	}
432 
433 	switch (param->type) {
434 		case STR_PARAM:
435 			if (phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_recursive_watchpoint TSRMLS_CC) != FAILURE) {
436 				phpdbg_notice("Set recursive watchpoint on %.*s", (int)param->len, param->str);
437 			}
438 			break;
439 
440 		phpdbg_default_switch_case();
441 	}
442 
443 	return SUCCESS;
444 } /* }}} */
445 
PHPDBG_WATCH(array)446 PHPDBG_WATCH(array) /* {{{ */
447 {
448 	if (phpdbg_rebuild_symtable(TSRMLS_C) == FAILURE) {
449 		return SUCCESS;
450 	}
451 
452 	switch (param->type) {
453 		case STR_PARAM:
454 			if (phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_array_watchpoint TSRMLS_CC) != FAILURE) {
455 				phpdbg_notice("Set array watchpoint on %.*s", (int)param->len, param->str);
456 			}
457 			break;
458 
459 		phpdbg_default_switch_case();
460 	}
461 
462 	return SUCCESS;
463 } /* }}} */
464 
phpdbg_watch_HashTable_dtor(zval ** zv)465 void phpdbg_watch_HashTable_dtor(zval **zv) {
466 	phpdbg_btree_result *result;
467 	TSRMLS_FETCH();
468 
469 	zval_ptr_dtor_wrapper(zv);
470 
471 	if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong)*zv))) {
472 		phpdbg_watchpoint_t *watch = result->ptr;
473 
474 		PHPDBG_G(watchpoint_hit) = 1;
475 
476 		phpdbg_notice("%.*s was removed, removing watchpoint%s", (int)watch->str_len, watch->str, (watch->flags & PHPDBG_WATCH_RECURSIVE)?" recursively":"");
477 
478 		if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
479 			phpdbg_delete_watchpoint_recursive(watch, 0 TSRMLS_CC);
480 		} else {
481 			zend_hash_del(&PHPDBG_G(watchpoints), watch->str, watch->str_len);
482 		}
483 	}
484 }
485 
486 
phpdbg_create_var_watchpoint(char * input,size_t len TSRMLS_DC)487 int phpdbg_create_var_watchpoint(char *input, size_t len TSRMLS_DC) {
488 	if (phpdbg_rebuild_symtable(TSRMLS_C) == FAILURE) {
489 		return FAILURE;
490 	}
491 
492 	return phpdbg_watchpoint_parse_symtables(input, len, phpdbg_create_watchpoint TSRMLS_CC);
493 }
494 
phpdbg_delete_var_watchpoint(char * input,size_t len TSRMLS_DC)495 int phpdbg_delete_var_watchpoint(char *input, size_t len TSRMLS_DC) {
496 	if (phpdbg_rebuild_symtable(TSRMLS_C) == FAILURE) {
497 		return FAILURE;
498 	}
499 
500 	return phpdbg_watchpoint_parse_symtables(input, len, phpdbg_delete_watchpoint TSRMLS_CC);
501 }
502 
503 #ifdef _WIN32
phpdbg_watchpoint_segfault_handler(void * addr TSRMLS_DC)504 int phpdbg_watchpoint_segfault_handler(void *addr TSRMLS_DC) {
505 #else
506 int phpdbg_watchpoint_segfault_handler(siginfo_t *info, void *context TSRMLS_DC) {
507 #endif
508 	void *page;
509 	phpdbg_watch_memdump *dump;
510 	phpdbg_watchpoint_t *watch;
511 	size_t size;
512 
513 	watch = phpdbg_check_for_watchpoint(
514 #ifdef _WIN32
515 		addr
516 #else
517 		info->si_addr
518 #endif
519 		TSRMLS_CC);
520 
521 	if (watch == NULL) {
522 		return FAILURE;
523 	}
524 
525 	page = phpdbg_get_page_boundary(watch->addr.ptr);
526 	size = phpdbg_get_total_page_size(watch->addr.ptr, watch->size);
527 
528 	/* re-enable writing */
529 	mprotect(page, size, PROT_READ | PROT_WRITE);
530 
531 	dump = malloc(MEMDUMP_SIZE(size));
532 	dump->page = page;
533 	dump->size = size;
534 
535 	memcpy(&dump->data, page, size);
536 
537 	zend_llist_add_element(&PHPDBG_G(watchlist_mem), &dump);
538 
539 	return SUCCESS;
540 }
541 
542 void phpdbg_watchpoints_clean(TSRMLS_D) {
543 	zend_hash_clean(&PHPDBG_G(watchpoints));
544 }
545 
546 static void phpdbg_watch_dtor(void *pDest) {
547 	phpdbg_watchpoint_t *watch = *(phpdbg_watchpoint_t **)pDest;
548 	TSRMLS_FETCH();
549 
550 	phpdbg_deactivate_watchpoint(watch TSRMLS_CC);
551 	phpdbg_remove_watchpoint(watch TSRMLS_CC);
552 
553 	free(watch->str);
554 	free(watch->name_in_parent);
555 	efree(watch);
556 }
557 
558 static void phpdbg_watch_mem_dtor(void *llist_data) {
559 	phpdbg_watch_memdump *dump = *(phpdbg_watch_memdump **)llist_data;
560 
561 	/* Disble writing again */
562 	if (dump->reenable_writing) {
563 		mprotect(dump->page, dump->size, PROT_READ);
564 	}
565 
566 	free(*(void **)llist_data);
567 }
568 
569 void phpdbg_setup_watchpoints(TSRMLS_D) {
570 #if _SC_PAGE_SIZE
571 	phpdbg_pagesize = sysconf(_SC_PAGE_SIZE);
572 #elif _SC_PAGESIZE
573 	phpdbg_pagesize = sysconf(_SC_PAGESIZE);
574 #elif _SC_NUTC_OS_PAGESIZE
575 	phpdbg_pagesize = sysconf(_SC_NUTC_OS_PAGESIZE);
576 #else
577 	phpdbg_pagesize = 4096; /* common pagesize */
578 #endif
579 
580 	zend_llist_init(&PHPDBG_G(watchlist_mem), sizeof(void *), phpdbg_watch_mem_dtor, 1);
581 	phpdbg_btree_init(&PHPDBG_G(watchpoint_tree), sizeof(void *) * 8);
582 	phpdbg_btree_init(&PHPDBG_G(watch_HashTables), sizeof(void *) * 8);
583 	zend_hash_init(&PHPDBG_G(watchpoints), 8, NULL, phpdbg_watch_dtor, 0 ZEND_FILE_LINE_CC);
584 }
585 
586 static void phpdbg_print_changed_zval(phpdbg_watch_memdump *dump TSRMLS_DC) {
587 	/* fetch all changes between dump->page and dump->page + dump->size */
588 	phpdbg_btree_position pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), (zend_ulong)dump->page, (zend_ulong)dump->page + dump->size);
589 	phpdbg_btree_result *result, *htresult;
590 	int elementDiff;
591 	void *curTest;
592 
593 	dump->reenable_writing = 0;
594 
595 	while ((result = phpdbg_btree_next(&pos))) {
596 		phpdbg_watchpoint_t *watch = result->ptr, *htwatch;
597 		void *oldPtr = (char *)&dump->data + ((size_t)watch->addr.ptr - (size_t)dump->page);
598 		char reenable = 1;
599 
600 		if ((size_t)watch->addr.ptr < (size_t)dump->page || (size_t)watch->addr.ptr + watch->size > (size_t)dump->page + dump->size) {
601 			continue;
602 		}
603 
604 		/* Test if the zval was separated and if necessary move the watchpoint */
605 		if (zend_hash_find(watch->parent_container, watch->name_in_parent, watch->name_in_parent_len + 1, &curTest) == SUCCESS) {
606 			if (watch->type == WATCH_ON_HASHTABLE) {
607 				switch (Z_TYPE_PP((zval **)curTest)) {
608 					case IS_ARRAY:
609 						curTest = (void *)Z_ARRVAL_PP((zval **)curTest);
610 						break;
611 					case IS_OBJECT:
612 						curTest = (void *)Z_OBJPROP_PP((zval **)curTest);
613 						break;
614 				}
615 			} else {
616 				curTest = *(void **)curTest;
617 			}
618 
619 			if (curTest != watch->addr.ptr) {
620 				phpdbg_deactivate_watchpoint(watch TSRMLS_CC);
621 				phpdbg_remove_watchpoint(watch TSRMLS_CC);
622 				watch->addr.ptr = curTest;
623 				phpdbg_store_watchpoint(watch TSRMLS_CC);
624 				phpdbg_activate_watchpoint(watch TSRMLS_CC);
625 
626 				reenable = 0;
627 			}
628 		}
629 
630 		/* Show to the user what changed and delete watchpoint upon removal */
631 		if (memcmp(oldPtr, watch->addr.ptr, watch->size) != SUCCESS) {
632 			if (PHPDBG_G(flags) & PHPDBG_SHOW_REFCOUNTS || (watch->type == WATCH_ON_ZVAL && memcmp(oldPtr, watch->addr.zv, sizeof(zvalue_value))) || (watch->type == WATCH_ON_HASHTABLE
633 #if ZEND_DEBUG
634 			    && !watch->addr.ht->inconsistent
635 #endif
636 			    && zend_hash_num_elements((HashTable *)oldPtr) != zend_hash_num_elements(watch->addr.ht))) {
637 				PHPDBG_G(watchpoint_hit) = 1;
638 
639 				phpdbg_notice("Breaking on watchpoint %s", watch->str);
640 			}
641 
642 			switch (watch->type) {
643 				case WATCH_ON_ZVAL: {
644 					int removed = ((zval *)oldPtr)->refcount__gc != watch->addr.zv->refcount__gc && !zend_symtable_exists(watch->parent_container, watch->name_in_parent, watch->name_in_parent_len + 1);
645 					int show_value = memcmp(oldPtr, watch->addr.zv, sizeof(zvalue_value));
646 					int show_ref = ((zval *)oldPtr)->refcount__gc != watch->addr.zv->refcount__gc || ((zval *)oldPtr)->is_ref__gc != watch->addr.zv->is_ref__gc;
647 
648 					if (removed || show_value) {
649 						phpdbg_write("Old value: ");
650 						if ((Z_TYPE_P((zval *)oldPtr) == IS_ARRAY || Z_TYPE_P((zval *)oldPtr) == IS_OBJECT) && removed) {
651 							phpdbg_writeln("Value inaccessible, HashTable already destroyed");
652 						} else {
653 							zend_print_flat_zval_r((zval *)oldPtr TSRMLS_CC);
654 							phpdbg_writeln("");
655 						}
656 					}
657 					if (PHPDBG_G(flags) & PHPDBG_SHOW_REFCOUNTS && (removed || show_ref)) {
658 						phpdbg_writeln("Old refcount: %d; Old is_ref: %d", ((zval *)oldPtr)->refcount__gc, ((zval *)oldPtr)->is_ref__gc);
659 					}
660 
661 					/* check if zval was removed */
662 					if (removed) {
663 						phpdbg_notice("Watchpoint %s was unset, removing watchpoint", watch->str);
664 						zend_hash_del(&PHPDBG_G(watchpoints), watch->str, watch->str_len);
665 
666 						reenable = 0;
667 
668 						if (Z_TYPE_P((zval *)oldPtr) == IS_ARRAY || Z_TYPE_P((zval *)oldPtr) == IS_OBJECT) {
669 							goto remove_ht_watch;
670 						}
671 
672 						break;
673 					}
674 
675 					if (show_value) {
676 						phpdbg_write("New value: ");
677 						zend_print_flat_zval_r(watch->addr.zv TSRMLS_CC);
678 						phpdbg_writeln("");
679 					}
680 					if (PHPDBG_G(flags) & PHPDBG_SHOW_REFCOUNTS && show_ref) {
681 						phpdbg_writeln("New refcount: %d; New is_ref: %d", watch->addr.zv->refcount__gc, watch->addr.zv->is_ref__gc);
682 					}
683 
684 					if ((Z_TYPE_P(watch->addr.zv) == IS_ARRAY && Z_ARRVAL_P(watch->addr.zv) != Z_ARRVAL_P((zval *)oldPtr)) || (Z_TYPE_P(watch->addr.zv) != IS_OBJECT && Z_OBJ_HANDLE_P(watch->addr.zv) == Z_OBJ_HANDLE_P((zval *)oldPtr))) {
685 						/* add new watchpoints if necessary */
686 						if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
687 							phpdbg_create_recursive_watchpoint(watch TSRMLS_CC);
688 						}
689 					}
690 
691 					if ((Z_TYPE_P((zval *)oldPtr) != IS_ARRAY || Z_ARRVAL_P(watch->addr.zv) == Z_ARRVAL_P((zval *)oldPtr)) && (Z_TYPE_P((zval *)oldPtr) != IS_OBJECT || Z_OBJ_HANDLE_P(watch->addr.zv) == Z_OBJ_HANDLE_P((zval *)oldPtr))) {
692 						break;
693 					}
694 
695 remove_ht_watch:
696 					if ((htresult = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong)Z_ARRVAL_P((zval *)oldPtr)))) {
697 						htwatch = htresult->ptr;
698 						zend_hash_del(&PHPDBG_G(watchpoints), htwatch->str, htwatch->str_len);
699 					}
700 
701 					break;
702 				}
703 				case WATCH_ON_HASHTABLE:
704 
705 #if ZEND_DEBUG
706 					if (watch->addr.ht->inconsistent) {
707 						phpdbg_notice("Watchpoint %s was unset, removing watchpoint", watch->str);
708 						zend_hash_del(&PHPDBG_G(watchpoints), watch->str, watch->str_len);
709 
710 						reenable = 0;
711 
712 						break;
713 					}
714 #endif
715 
716 					elementDiff = zend_hash_num_elements((HashTable *)oldPtr) - zend_hash_num_elements(watch->addr.ht);
717 					if (elementDiff) {
718 						if (elementDiff > 0) {
719 							phpdbg_writeln("%d elements were removed from the array", elementDiff);
720 						} else {
721 							phpdbg_writeln("%d elements were added to the array", -elementDiff);
722 
723 							/* add new watchpoints if necessary */
724 							if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
725 								phpdbg_create_recursive_watchpoint(watch TSRMLS_CC);
726 							}
727 						}
728 					}
729 					if (((HashTable *)oldPtr)->pInternalPointer != watch->addr.ht->pInternalPointer) {
730 						phpdbg_writeln("Internal pointer of array was changed");
731 					}
732 					break;
733 			}
734 		}
735 
736 		dump->reenable_writing = dump->reenable_writing | reenable;
737 	}
738 }
739 
740 int phpdbg_print_changed_zvals(TSRMLS_D) {
741 	zend_llist_position pos;
742 	phpdbg_watch_memdump **dump;
743 	int ret;
744 
745 	if (zend_llist_count(&PHPDBG_G(watchlist_mem)) == 0) {
746 		return FAILURE;
747 	}
748 
749 	dump = (phpdbg_watch_memdump **)zend_llist_get_last_ex(&PHPDBG_G(watchlist_mem), &pos);
750 
751 	do {
752 		phpdbg_print_changed_zval(*dump TSRMLS_CC);
753 	} while ((dump = (phpdbg_watch_memdump **)zend_llist_get_prev_ex(&PHPDBG_G(watchlist_mem), &pos)));
754 
755 	zend_llist_clean(&PHPDBG_G(watchlist_mem));
756 
757 	ret = PHPDBG_G(watchpoint_hit)?SUCCESS:FAILURE;
758 	PHPDBG_G(watchpoint_hit) = 0;
759 
760 	return ret;
761 }
762 
763 void phpdbg_list_watchpoints(TSRMLS_D) {
764 	HashPosition position;
765 	phpdbg_watchpoint_t **watch;
766 
767 	for (zend_hash_internal_pointer_reset_ex(&PHPDBG_G(watchpoints), &position);
768 	     zend_hash_get_current_data_ex(&PHPDBG_G(watchpoints), (void**) &watch, &position) == SUCCESS;
769 	     zend_hash_move_forward_ex(&PHPDBG_G(watchpoints), &position)) {
770 		phpdbg_writeln("%.*s", (int)(*watch)->str_len, (*watch)->str);
771 	}
772 }
773 
774 void phpdbg_watch_efree(void *ptr) {
775 	phpdbg_btree_result *result;
776 	TSRMLS_FETCH();
777 
778 	result = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), (zend_ulong)ptr);
779 
780 	if (result) {
781 		phpdbg_watchpoint_t *watch = result->ptr;
782 
783 		if ((size_t)watch->addr.ptr + watch->size > (size_t)ptr) {
784 			zend_hash_del(&PHPDBG_G(watchpoints), watch->str, watch->str_len);
785 		}
786 	}
787 
788 	PHPDBG_G(original_free_function)(ptr);
789 }
790