xref: /PHP-7.0/sapi/phpdbg/phpdbg_watch.c (revision 478f119a)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2017 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 /* Some information for the reader...
22  *
23  * Watchpoints are either simple, recursive or implicit (PHPDBG_WATCH_* flags)
24  * Simple means that a particular watchpoint was explicitely defined
25  * Recursive watchpoints are created recursively and substitute simple watchpoints
26  * Implicit watchpoints are implicitely created on all ancestors of simple or recursive watchpoints
27  * Recursive and (simple or implicit) watchpoints are mutually exclusive
28  *
29  * PHPDBG_G(watchpoint_tree) contains all watchpoints identified by the watch target address
30  * PHPDBG_G(watch_HashTables) contains the dtors of the HashTables to call in our custom dtor (we substitute the dtor of HashTables containing watched zvals by our own dtor)
31  * PHPDBG_G(watchpoints) contains all watchpoints (except the ones managed by watch collision)
32  * PHPDBG_G(watch_collisions) is indexed by a zend_reference * pointer. It stores information about collisions (everything which contains a zend_reference * may be referenced by multiple watches)
33  *
34  * Creating a watchpoint:
35  * * Create watchpoints with PHPDBG_WATCH_IMPLICIT set on each zval and HashTable in hierarchy except the last zval or HashTable fetch. (if already existing PHPDBG_WATCH_IMPLICIT flag is added)
36  * * Create a PHPDBG_WATCH_SIMPLE watch for simple watches or a PHPDBG_WATCH_RECURSIVE watch for recursive watches
37  * * When the target zval is an IS_REFERENCE, create a watchpoint on it too
38  * * Each time a watchpoints parent is a zval and it is Z_REFCOUNTED(), put a watchpoint (WATCH_ON_REFCOUNTED) on it and add a watchpoint collision
39  * * When in recursive mode and encountering a not-refcounted PHPDBG_WATCH_SIMPLE, remove it and recreate it with a PHPDBG_WATCH_RECURSIVE (handled via watch collision)
40  * * Make attention to not add something twice or iterate over it twice
41  *
42  * Deleting a watchpoint:
43  * * Only allow deletion of recursive watches at their root and simple watches
44  * * Go to referenced variable. And remove watch collision on *parent* (if there is a parent)
45  * * If it is Z_REFCOUNTED(), remove that watch collision
46  *
47  * Watch collisions:
48  * * hold a counter for recursive, if it is incremented from 0 to 1, create recursive watchpoint
49  * * holds a HashTable for normal (not implicit) watchpoints ... it is used to get the fetch type of the HashTable (depending on whether it is empty or not)
50  * * holds a HashTable for implicit watchpoints ... (some sort of a refcounter, but ensure that there are no duplicates)
51  * * if normal and implicit watchpoints are empty, drop that watch collision and remove WATCH_ON_REFCOUNT alongside with watchpoint on an eventual reference
52  *
53  * Watching on addresses:
54  * * Address and size are transformed into memory page aligned address and size
55  * * mprotect() enables or disables them (depending on flags) - Windows has a transparent compatibility layer in phpdbg_win.c
56  * * segfault handler dumps watched memory segment and deactivates watchpoint
57  * * later watches inside these memory segments are compared against their current value and eventually reactivated (or deleted)
58  *
59  * A watched zval was removed:
60  * * trigger a memory copy (in segv handler) and an automatic deactivation
61  * * change a type flag _zval_struct.u1.v.type_flags (add PHPDBG_DESTRUCTED_ZVAL flag) in memory dump
62  *
63  * A watched zval was changed:
64  * * check if parent container has a different reference for referenced zval - recursively update all watches and drop them if necessary
65  * * if _zval_struct.u1.v.type_flags & PHPDBG_DESTRUCTED_ZVAL, add it to a list of zvals to be handled at the end (if location was not changed, remove it finally)
66  * * display changes if watch->flags & PHPDBG_WATCH_NORMAL (means: not implicit)
67  * * handle case where Z_RECOUNTED() or Z_PTR() changed (remove/add collison(s))
68  * * if necessary ... on zvals: handle references, if recursive also objects and arrays ... on arrays: if recursive, add new elements
69  * * drop destructed zval watchpoints which were not updated
70  */
71 
72 #include "zend.h"
73 #include "phpdbg.h"
74 #include "phpdbg_btree.h"
75 #include "phpdbg_watch.h"
76 #include "phpdbg_utils.h"
77 #include "phpdbg_prompt.h"
78 #ifndef _WIN32
79 # include <unistd.h>
80 # include <sys/mman.h>
81 #endif
82 
83 ZEND_EXTERN_MODULE_GLOBALS(phpdbg)
84 
85 const phpdbg_command_t phpdbg_watch_commands[] = {
86 	PHPDBG_COMMAND_D_EX(array,      "create watchpoint on an array", 'a', watch_array,     &phpdbg_prompt_commands[24], "s", 0),
87 	PHPDBG_COMMAND_D_EX(delete,     "delete watchpoint",             'd', watch_delete,    &phpdbg_prompt_commands[24], "s", 0),
88 	PHPDBG_COMMAND_D_EX(recursive,  "create recursive watchpoints",  'r', watch_recursive, &phpdbg_prompt_commands[24], "s", 0),
89 	PHPDBG_END_COMMAND
90 };
91 
92 //#define HT_FROM_WATCH(watch) (watch->type == WATCH_ON_OBJECT ? watch->addr.obj->handlers->get_properties(watch->parent_container.zv) : watch->type == WATCH_ON_ARRAY ? &watch->addr.arr->ht : NULL)
93 #define HT_FROM_ZVP(zvp) (Z_TYPE_P(zvp) == IS_OBJECT ? Z_OBJPROP_P(zvp) : Z_TYPE_P(zvp) == IS_ARRAY ? Z_ARRVAL_P(zvp) : NULL)
94 
95 #define HT_WATCH_OFFSET (sizeof(zend_refcounted *) + sizeof(uint32_t)) /* we are not interested in gc and flags */
96 #define HT_PTR_HT(ptr) ((HashTable *) (((char *) (ptr)) - HT_WATCH_OFFSET))
97 #define HT_WATCH_HT(watch) HT_PTR_HT((watch)->addr.ptr)
98 
99 typedef struct {
100 	void *page;
101 	size_t size;
102 	char reenable_writing;
103 	/* data must be last element */
104 	void *data;
105 } phpdbg_watch_memdump;
106 
107 #define MEMDUMP_SIZE(size) (sizeof(phpdbg_watch_memdump) - sizeof(void *) + (size))
108 
109 
phpdbg_check_for_watchpoint(void * addr)110 static phpdbg_watchpoint_t *phpdbg_check_for_watchpoint(void *addr) {
111 	phpdbg_watchpoint_t *watch;
112 	phpdbg_btree_result *result = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), (zend_ulong)phpdbg_get_page_boundary(addr) + phpdbg_pagesize - 1);
113 
114 	if (result == NULL) {
115 		return NULL;
116 	}
117 
118 	watch = result->ptr;
119 
120 	/* check if that addr is in a mprotect()'ed memory area */
121 	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) {
122 		/* failure */
123 		return NULL;
124 	}
125 
126 	return watch;
127 }
128 
phpdbg_change_watchpoint_access(phpdbg_watchpoint_t * watch,int access)129 static void phpdbg_change_watchpoint_access(phpdbg_watchpoint_t *watch, int access) {
130 	/* pagesize is assumed to be in the range of 2^x */
131 	mprotect(phpdbg_get_page_boundary(watch->addr.ptr), phpdbg_get_total_page_size(watch->addr.ptr, watch->size), access);
132 }
133 
phpdbg_activate_watchpoint(phpdbg_watchpoint_t * watch)134 static inline void phpdbg_activate_watchpoint(phpdbg_watchpoint_t *watch) {
135 	phpdbg_change_watchpoint_access(watch, PROT_READ);
136 }
137 
phpdbg_deactivate_watchpoint(phpdbg_watchpoint_t * watch)138 static inline void phpdbg_deactivate_watchpoint(phpdbg_watchpoint_t *watch) {
139 	phpdbg_change_watchpoint_access(watch, PROT_READ | PROT_WRITE);
140 }
141 
phpdbg_store_watchpoint(phpdbg_watchpoint_t * watch)142 static inline void phpdbg_store_watchpoint(phpdbg_watchpoint_t *watch) {
143 	phpdbg_btree_insert(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr, watch);
144 }
145 
phpdbg_remove_watchpoint(phpdbg_watchpoint_t * watch)146 static inline void phpdbg_remove_watchpoint(phpdbg_watchpoint_t *watch) {
147 	phpdbg_btree_delete(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr);
148 }
149 
phpdbg_create_addr_watchpoint(void * addr,size_t size,phpdbg_watchpoint_t * watch)150 void phpdbg_create_addr_watchpoint(void *addr, size_t size, phpdbg_watchpoint_t *watch) {
151 	watch->addr.ptr = addr;
152 	watch->size = size;
153 }
154 
phpdbg_create_zval_watchpoint(zval * zv,phpdbg_watchpoint_t * watch)155 void phpdbg_create_zval_watchpoint(zval *zv, phpdbg_watchpoint_t *watch) {
156 	phpdbg_create_addr_watchpoint(zv, sizeof(zval), watch);
157 	watch->type = WATCH_ON_ZVAL;
158 }
159 
phpdbg_create_ht_watchpoint(HashTable * ht,phpdbg_watchpoint_t * watch)160 void phpdbg_create_ht_watchpoint(HashTable *ht, phpdbg_watchpoint_t *watch) {
161 	phpdbg_create_addr_watchpoint(((char *) ht) + HT_WATCH_OFFSET, sizeof(HashTable) - HT_WATCH_OFFSET, watch);
162 	watch->type = WATCH_ON_HASHTABLE;
163 	watch->implicit_ht_count = 0;
164 }
165 
166 static int phpdbg_create_recursive_ht_watch(phpdbg_watchpoint_t *watch);
167 static int phpdbg_create_recursive_zval_watch(phpdbg_watchpoint_t *watch);
168 
169 void phpdbg_watch_HashTable_dtor(zval *ptr);
170 
phpdbg_free_watch(phpdbg_watchpoint_t * watch)171 static void phpdbg_free_watch(phpdbg_watchpoint_t *watch) {
172 	zend_string_release(watch->str);
173 	zend_string_release(watch->name_in_parent);
174 }
175 
176 static int phpdbg_delete_watchpoint(phpdbg_watchpoint_t *tmp_watch);
177 static void phpdbg_delete_ht_watchpoints_recursive(phpdbg_watchpoint_t *watch);
178 static void phpdbg_delete_zval_watchpoints_recursive(phpdbg_watchpoint_t *watch);
179 static void phpdbg_delete_watchpoints_recursive(phpdbg_watchpoint_t *watch);
180 
181 /* Store all the possible watches the refcounted may refer to (for displaying & deleting by identifier) [collision] */
phpdbg_create_refcounted_watchpoint(phpdbg_watchpoint_t * parent,zend_refcounted * ref)182 static phpdbg_watchpoint_t *phpdbg_create_refcounted_watchpoint(phpdbg_watchpoint_t *parent, zend_refcounted *ref) {
183 	phpdbg_watchpoint_t *watch = emalloc(sizeof(phpdbg_watchpoint_t));
184 	watch->flags = parent->flags;
185 	watch->parent = parent;
186 	watch->str = parent->str;
187 	++GC_REFCOUNT(parent->str);
188 	phpdbg_create_addr_watchpoint(&GC_REFCOUNT(ref), sizeof(uint32_t), watch);
189 	watch->type = WATCH_ON_REFCOUNTED;
190 
191 	return watch;
192 }
193 
194 /* Must prevent duplicates ... if there are duplicates, replace new by old! */
phpdbg_add_watch_collision(phpdbg_watchpoint_t * watch)195 static void phpdbg_add_watch_collision(phpdbg_watchpoint_t *watch) {
196 	phpdbg_watch_collision *cur;
197 
198 	/* Check for either recursive or (simple and/or implicit) */
199 	ZEND_ASSERT(((watch->flags & PHPDBG_WATCH_RECURSIVE) == 0) ^ ((watch->flags & (PHPDBG_WATCH_IMPLICIT | PHPDBG_WATCH_SIMPLE)) == 0));
200 
201 	if ((cur = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->addr.ref))) {
202 		phpdbg_watchpoint_t *old;
203 		int flags = 0;
204 		if ((old = zend_hash_find_ptr(&cur->watches, watch->str)) || (old = zend_hash_find_ptr(&cur->implicit_watches, watch->str))) {
205 			if (((old->flags ^ watch->flags) & (PHPDBG_WATCH_NORMAL|PHPDBG_WATCH_IMPLICIT)) == 0) {
206 				return; /* there was no change ... */
207 			}
208 
209 			flags = old->flags;
210 
211 			if (flags & PHPDBG_WATCH_RECURSIVE) {
212 				if (!(watch->flags & PHPDBG_WATCH_RECURSIVE) && !--cur->refs) {
213 					phpdbg_delete_watchpoints_recursive(watch);
214 				}
215 			}
216 			if (flags & PHPDBG_WATCH_NORMAL) {
217 				zend_hash_del(&cur->watches, watch->str);
218 				if (zend_hash_num_elements(&cur->watches) > 0) {
219 					cur->watch = Z_PTR_P(zend_hash_get_current_data_ex(&cur->watches, NULL));
220 				} else {
221 					cur->watch = Z_PTR_P(zend_hash_get_current_data_ex(&cur->implicit_watches, NULL));
222 				}
223 			}
224 			if (flags & PHPDBG_WATCH_IMPLICIT) {
225 				zend_hash_del(&cur->implicit_watches, watch->str);
226 			}
227 
228 			old->flags = watch->flags;
229 			phpdbg_free_watch(watch);
230 			efree(watch);
231 			watch = old;
232 		}
233 		if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
234 			if (!(flags & PHPDBG_WATCH_RECURSIVE) && !cur->refs++) {
235 				phpdbg_create_recursive_zval_watch(watch->parent);
236 			}
237 		}
238 	} else {
239 		phpdbg_watch_collision coll;
240 		coll.refs = (watch->flags & PHPDBG_WATCH_RECURSIVE) != 0;
241 		coll.watch = watch;
242 		zend_hash_init(&coll.watches, 8, arghs, NULL, 0);
243 		zend_hash_init(&coll.implicit_watches, 8, ..., NULL, 0);
244 		cur = zend_hash_index_add_mem(&PHPDBG_G(watch_collisions), (zend_ulong) watch->addr.ref, &coll, sizeof(phpdbg_watch_collision));
245 		phpdbg_store_watchpoint(cur->watch);
246 		phpdbg_activate_watchpoint(cur->watch);
247 		if (coll.refs) {
248 			phpdbg_create_recursive_zval_watch(watch->parent);
249 		}
250 	}
251 
252 	if (watch->flags & PHPDBG_WATCH_NORMAL) {
253 		cur->watch = watch;
254 		zend_hash_add_ptr(&cur->watches, watch->str, watch->parent);
255 	}
256 	if (watch->flags & PHPDBG_WATCH_IMPLICIT) {
257 		zend_hash_add_ptr(&cur->implicit_watches, watch->str, watch->parent);
258 	}
259 }
260 
phpdbg_remove_watch_collision(phpdbg_watchpoint_t * watch)261 static void phpdbg_remove_watch_collision(phpdbg_watchpoint_t *watch) {
262 	phpdbg_watch_collision *cur;
263 	if ((cur = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) Z_COUNTED_P(watch->addr.zv)))) {
264 		if (cur->refs && !--cur->refs) {
265 			phpdbg_delete_watchpoints_recursive(watch);
266 		}
267 
268 		zend_hash_del(&cur->watches, watch->str);
269 		zend_hash_del(&cur->implicit_watches, watch->str);
270 
271 		if (zend_hash_num_elements(&cur->watches) > 0) {
272 			cur->watch = Z_PTR_P(zend_hash_get_current_data_ex(&cur->watches, NULL));
273 		} else if (zend_hash_num_elements(&cur->implicit_watches) > 0) {
274 			cur->watch = Z_PTR_P(zend_hash_get_current_data_ex(&cur->implicit_watches, NULL));
275 		} else {
276 			phpdbg_deactivate_watchpoint(cur->watch);
277 			phpdbg_remove_watchpoint(cur->watch);
278 
279 			zend_hash_index_del(&PHPDBG_G(watch_collisions), (zend_ulong) Z_COUNTED_P(watch->addr.zv));
280 		}
281 	}
282 }
283 
284 static phpdbg_watchpoint_t *phpdbg_create_watchpoint(phpdbg_watchpoint_t *watch);
285 
phpdbg_create_reference_watch(phpdbg_watchpoint_t * watch)286 static phpdbg_watchpoint_t *phpdbg_create_reference_watch(phpdbg_watchpoint_t *watch) {
287 	phpdbg_watchpoint_t *ref = emalloc(sizeof(phpdbg_watchpoint_t));
288 	watch->reference = ref;
289 	ref->flags = watch->flags;
290 	ref->str = watch->str;
291 	++GC_REFCOUNT(ref->str);
292 	ref->parent = watch;
293 	ref->parent_container = NULL;
294 	phpdbg_create_zval_watchpoint(Z_REFVAL_P(watch->addr.zv), ref);
295 
296 	phpdbg_create_watchpoint(ref);
297 
298 	return ref;
299 }
300 
phpdbg_get_refcount_watch(phpdbg_watchpoint_t * parent)301 static phpdbg_watchpoint_t *phpdbg_get_refcount_watch(phpdbg_watchpoint_t *parent) {
302 	zend_refcounted *ref;
303 	phpdbg_btree_result *res;
304 
305 	if (parent->type == WATCH_ON_HASHTABLE) {
306 		parent = parent->parent;
307 		if (!parent) {
308 			return NULL;
309 		}
310 	}
311 
312 	ZEND_ASSERT(parent->type == WATCH_ON_ZVAL);
313 	ref = Z_COUNTED_P(parent->addr.zv);
314 
315 	res = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) ref);
316 	if (res) {
317 		return res->ptr;
318 	}
319 	return NULL;
320 }
321 
phpdbg_create_watchpoint(phpdbg_watchpoint_t * watch)322 static phpdbg_watchpoint_t *phpdbg_create_watchpoint(phpdbg_watchpoint_t *watch) {
323 	phpdbg_watchpoint_t *ret = watch;
324 
325 	if (watch->type == WATCH_ON_ZVAL) {
326 		switch (Z_TYPE_P(watch->addr.zv)) {
327 			case IS_NULL:
328 			case IS_UNDEF:
329 			case IS_TRUE:
330 			case IS_FALSE:
331 				memset(watch->addr.zv, 0, sizeof(zend_value));
332 		}
333 	}
334 
335 	/* exclude references & refcounted */
336 	if (!watch->parent || watch->parent->type != WATCH_ON_ZVAL || watch->type == WATCH_ON_HASHTABLE) {
337 		phpdbg_watchpoint_t *old_watch = zend_hash_find_ptr(&PHPDBG_G(watchpoints), watch->str);
338 
339 		if (old_watch) {
340 #define return_and_free_watch(x) { \
341 	phpdbg_watchpoint_t *ref = phpdbg_get_refcount_watch(old_watch); \
342 	if (ref) { \
343 		phpdbg_add_watch_collision(ref); \
344 	} \
345 	if (watch != old_watch) { \
346 		phpdbg_free_watch(watch); \
347 		efree(watch); \
348 	} \
349 	return (x); \
350 }
351 			if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
352 				if (old_watch->flags & PHPDBG_WATCH_RECURSIVE) {
353 					return_and_free_watch(NULL);
354 				} else {
355 					old_watch->flags &= ~(PHPDBG_WATCH_SIMPLE | PHPDBG_WATCH_IMPLICIT);
356 					old_watch->flags |= PHPDBG_WATCH_RECURSIVE;
357 					return_and_free_watch(old_watch);
358 				}
359 			} else {
360 				if (!(old_watch->flags & PHPDBG_WATCH_RECURSIVE)) {
361 					old_watch->flags |= watch->flags & (PHPDBG_WATCH_IMPLICIT | PHPDBG_WATCH_SIMPLE);
362 				}
363 				return_and_free_watch(NULL);
364 			}
365 		} else {
366 			if (watch->parent && watch->parent->type == WATCH_ON_HASHTABLE) {
367 				watch->parent->implicit_ht_count++;
368 			}
369 			zend_hash_add_ptr(&PHPDBG_G(watchpoints), watch->str, watch);
370 		}
371 	}
372 
373 	phpdbg_store_watchpoint(watch);
374 
375 	if (watch->parent && watch->parent->type == WATCH_ON_ZVAL && Z_REFCOUNTED_P(watch->parent->addr.zv)) {
376 		phpdbg_add_watch_collision(phpdbg_create_refcounted_watchpoint(watch, Z_COUNTED_P(watch->parent->addr.zv)));
377 	}
378 
379 	if (watch->type == WATCH_ON_ZVAL) {
380 		if (watch->parent_container) {
381 			HashTable *ht_watches;
382 			phpdbg_btree_result *find;
383 			if (!(find = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) watch->parent_container))) {
384 				phpdbg_watch_ht_info *hti = emalloc(sizeof(*hti));
385 				hti->dtor = watch->parent_container->pDestructor;
386 				ht_watches = &hti->watches;
387 				zend_hash_init(ht_watches, 0, grrrrr, ZVAL_PTR_DTOR, 0);
388 				phpdbg_btree_insert(&PHPDBG_G(watch_HashTables), (zend_ulong) watch->parent_container, hti);
389 				watch->parent_container->pDestructor = (dtor_func_t) phpdbg_watch_HashTable_dtor;
390 			} else {
391 				ht_watches = &((phpdbg_watch_ht_info *) find->ptr)->watches;
392 			}
393 			zend_hash_add_ptr(ht_watches, watch->name_in_parent, watch);
394 		}
395 
396 		if (Z_ISREF_P(watch->addr.zv)) {
397 			ret = phpdbg_create_reference_watch(watch);
398 		}
399 	}
400 
401 	phpdbg_activate_watchpoint(watch);
402 
403 	return ret;
404 }
405 
phpdbg_create_simple_watchpoint(phpdbg_watchpoint_t * watch)406 static int phpdbg_create_simple_watchpoint(phpdbg_watchpoint_t *watch) {
407 	watch->flags |= PHPDBG_WATCH_SIMPLE;
408 
409 	phpdbg_create_watchpoint(watch);
410 
411 	return SUCCESS;
412 }
413 
phpdbg_create_array_watchpoint(phpdbg_watchpoint_t * zv_watch)414 static int phpdbg_create_array_watchpoint(phpdbg_watchpoint_t *zv_watch) {
415 	zval *zv = zv_watch->addr.zv;
416 	phpdbg_watchpoint_t *watch = emalloc(sizeof(phpdbg_watchpoint_t));
417 	HashTable *ht = HT_FROM_ZVP(zv);
418 
419 	watch->parent = zv_watch;
420 
421 	if (!ht) {
422 		return FAILURE;
423 	}
424 
425 	phpdbg_create_ht_watchpoint(ht, watch);
426 
427 	if (phpdbg_create_watchpoint(watch) == NULL) {
428 		return SUCCESS;
429 	}
430 
431 	if (Z_TYPE_P(zv) == IS_ARRAY) {
432 		watch->flags |= PHPDBG_WATCH_ARRAY;
433 	} else {
434 		watch->flags |= PHPDBG_WATCH_OBJECT;
435 	}
436 
437 	phpdbg_add_watch_collision(phpdbg_create_refcounted_watchpoint(watch, Z_COUNTED_P(zv)));
438 
439 	return SUCCESS;
440 }
441 
phpdbg_create_recursive_watchpoint(phpdbg_watchpoint_t * watch)442 static int phpdbg_create_recursive_watchpoint(phpdbg_watchpoint_t *watch) {
443 	if (watch->type != WATCH_ON_ZVAL) {
444 		return FAILURE;
445 	}
446 
447 	watch->flags |= PHPDBG_WATCH_RECURSIVE;
448 	watch = phpdbg_create_watchpoint(watch);
449 
450 	return SUCCESS;
451 }
452 
phpdbg_create_recursive_ht_watch(phpdbg_watchpoint_t * watch)453 static int phpdbg_create_recursive_ht_watch(phpdbg_watchpoint_t *watch) {
454 	zval *zv;
455 	zend_string *key;
456 	zend_long h;
457 
458 	ZEND_ASSERT(watch->type == WATCH_ON_HASHTABLE);
459 
460 	ZEND_HASH_FOREACH_KEY_VAL(HT_WATCH_HT(watch), h, key, zv) {
461 		phpdbg_watchpoint_t *new_watch = emalloc(sizeof(phpdbg_watchpoint_t));
462 
463 		new_watch->flags = PHPDBG_WATCH_RECURSIVE;
464 		new_watch->parent = watch;
465 		new_watch->parent_container = HT_WATCH_HT(watch);
466 
467 		if (key) {
468 			new_watch->name_in_parent = key;
469 			++GC_REFCOUNT(key);
470 		} else {
471 			new_watch->name_in_parent = strpprintf(0, ZEND_LONG_FMT, h);
472 		}
473 
474 		new_watch->str = strpprintf(0, "%.*s%s%s%s", (int) ZSTR_LEN(watch->str) - 2, ZSTR_VAL(watch->str), (watch->flags & PHPDBG_WATCH_ARRAY) ? "[" : "->", phpdbg_get_property_key(ZSTR_VAL(new_watch->name_in_parent)), (watch->flags & PHPDBG_WATCH_ARRAY) ? "]" : "");
475 
476 		while (Z_TYPE_P(zv) == IS_INDIRECT) {
477 			zv = Z_INDIRECT_P(zv);
478 		}
479 
480 		phpdbg_create_zval_watchpoint(zv, new_watch);
481 		new_watch = phpdbg_create_watchpoint(new_watch);
482 		phpdbg_create_recursive_zval_watch(new_watch);
483 	} ZEND_HASH_FOREACH_END();
484 
485 	return SUCCESS;
486 }
487 
phpdbg_create_recursive_zval_watch(phpdbg_watchpoint_t * watch)488 static int phpdbg_create_recursive_zval_watch(phpdbg_watchpoint_t *watch) {
489 	HashTable *ht;
490 	zval *zvp;
491 
492 	ZEND_ASSERT(watch->type == WATCH_ON_ZVAL);
493 
494 	zvp = watch->addr.zv;
495 	ZVAL_DEREF(zvp);
496 
497 	if ((ht = HT_FROM_ZVP(zvp))) {
498 		phpdbg_watchpoint_t *new_watch = emalloc(sizeof(phpdbg_watchpoint_t));
499 
500 		new_watch->flags = PHPDBG_WATCH_RECURSIVE;
501 		new_watch->parent = watch;
502 		new_watch->parent_container = watch->parent_container;
503 		new_watch->name_in_parent = watch->name_in_parent;
504 		++GC_REFCOUNT(new_watch->name_in_parent);
505 		new_watch->str = strpprintf(0, "%.*s[]", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str));
506 
507 		if (Z_TYPE_P(zvp) == IS_ARRAY) {
508 			new_watch->flags |= PHPDBG_WATCH_ARRAY;
509 		} else {
510 			new_watch->flags |= PHPDBG_WATCH_OBJECT;
511 		}
512 
513 		phpdbg_create_ht_watchpoint(ht, new_watch);
514 
515 		phpdbg_create_recursive_ht_watch(new_watch);
516 
517 		phpdbg_create_watchpoint(new_watch);
518 	}
519 
520 	return SUCCESS;
521 }
522 
phpdbg_delete_implicit_parents(phpdbg_watchpoint_t * watch)523 static void phpdbg_delete_implicit_parents(phpdbg_watchpoint_t *watch) {
524 	phpdbg_watchpoint_t *parent = watch->parent;
525 	if (!parent) {
526 		return;
527 	}
528 
529 	ZEND_ASSERT(!(parent->flags & PHPDBG_WATCH_RECURSIVE));
530 	ZEND_ASSERT(parent->flags & PHPDBG_WATCH_IMPLICIT);
531 
532 	if (parent->type == WATCH_ON_HASHTABLE && --parent->implicit_ht_count) {
533 		return;
534 	}
535 
536 	parent->flags &= ~PHPDBG_WATCH_IMPLICIT;
537 	if (!(parent->flags & PHPDBG_WATCH_SIMPLE)) {
538 		if (parent->type == WATCH_ON_ZVAL && Z_REFCOUNTED_P(watch->addr.zv)) {
539 			phpdbg_remove_watch_collision(parent);
540 		}
541 		zend_hash_del(&PHPDBG_G(watchpoints), parent->str);
542 	}
543 }
544 
545 /* delete watchpoint, recursively (and inclusively) */
phpdbg_delete_watchpoint_recursive(phpdbg_watchpoint_t * watch,zend_bool user_request)546 static int phpdbg_delete_watchpoint_recursive(phpdbg_watchpoint_t *watch, zend_bool user_request) {
547 	if (watch->type == WATCH_ON_HASHTABLE) {
548 		if (user_request) {
549 			phpdbg_delete_ht_watchpoints_recursive(watch);
550 		} else {
551 			HashTable *ht;
552 			phpdbg_btree_result *result;
553 
554 			ht = HT_FROM_ZVP(watch->addr.zv);
555 
556 			if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) ht))) {
557 				phpdbg_delete_watchpoint_recursive((phpdbg_watchpoint_t *) result->ptr, user_request);
558 			}
559 		}
560 	} else if (watch->type == WATCH_ON_ZVAL) {
561 		phpdbg_delete_zval_watchpoints_recursive(watch);
562 	}
563 
564 	return zend_hash_del(&PHPDBG_G(watchpoints), watch->str);
565 }
566 
phpdbg_delete_ht_watchpoints_recursive(phpdbg_watchpoint_t * watch)567 static void phpdbg_delete_ht_watchpoints_recursive(phpdbg_watchpoint_t *watch) {
568 	zend_string *str, *strkey;
569 	zend_long numkey;
570 	phpdbg_watchpoint_t *watchpoint;
571 
572 	ZEND_HASH_FOREACH_KEY(HT_WATCH_HT(watch), numkey, strkey) {
573 		if (strkey) {
574 			str = strpprintf(0, "%.*s%s%s%s", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str), (watch->flags & PHPDBG_WATCH_ARRAY) ? "[" : "->", phpdbg_get_property_key(ZSTR_VAL(strkey)), (watch->flags & PHPDBG_WATCH_ARRAY) ? "]" : "");
575 		} else {
576 			str = strpprintf(0, "%.*s%s" ZEND_LONG_FMT "%s", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str), (watch->flags & PHPDBG_WATCH_ARRAY) ? "[" : "->", numkey, (watch->flags & PHPDBG_WATCH_ARRAY) ? "]" : "");
577 		}
578 
579 		if ((watchpoint = zend_hash_find_ptr(&PHPDBG_G(watchpoints), str))) {
580 			phpdbg_delete_watchpoint_recursive(watchpoint, 1);
581 		}
582 
583 		zend_string_release(str);
584 	} ZEND_HASH_FOREACH_END();
585 }
586 
phpdbg_delete_zval_watchpoints_recursive(phpdbg_watchpoint_t * watch)587 static void phpdbg_delete_zval_watchpoints_recursive(phpdbg_watchpoint_t *watch) {
588 	if (Z_REFCOUNTED_P(watch->addr.zv)) {
589 		phpdbg_remove_watch_collision(watch);
590 	}
591 }
592 
593 /* delete watchpoints recusively (exclusively!) */
phpdbg_delete_watchpoints_recursive(phpdbg_watchpoint_t * watch)594 static void phpdbg_delete_watchpoints_recursive(phpdbg_watchpoint_t *watch) {
595 	if (watch->type == WATCH_ON_ZVAL) {
596 		phpdbg_delete_zval_watchpoints_recursive(watch);
597 	} else if (watch->type == WATCH_ON_HASHTABLE) {
598 		phpdbg_delete_ht_watchpoints_recursive(watch);
599 	}
600 }
601 
phpdbg_delete_watchpoint(phpdbg_watchpoint_t * tmp_watch)602 static int phpdbg_delete_watchpoint(phpdbg_watchpoint_t *tmp_watch) {
603 	int ret;
604 	phpdbg_watchpoint_t *watch;
605 	phpdbg_btree_result *result;
606 
607 	if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) tmp_watch->addr.ptr)) == NULL) {
608 		return FAILURE;
609 	}
610 
611 	watch = result->ptr;
612 
613 	if (!(watch->flags & PHPDBG_WATCH_NORMAL) || (watch->parent && (watch->parent->flags & PHPDBG_WATCH_RECURSIVE))) {
614 		return FAILURE; /* TODO: better error message for recursive (??) */
615 	}
616 
617 	if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
618 		ret = phpdbg_delete_watchpoint_recursive(watch, 1);
619 	} else {
620 		ret = zend_hash_del(&PHPDBG_G(watchpoints), watch->str);
621 	}
622 
623 	phpdbg_free_watch(tmp_watch);
624 	efree(tmp_watch);
625 
626 	return ret;
627 }
628 
phpdbg_watchpoint_parse_wrapper(char * name,size_t namelen,char * key,size_t keylen,HashTable * parent,zval * zv,int (* callback)(phpdbg_watchpoint_t *))629 static int phpdbg_watchpoint_parse_wrapper(char *name, size_t namelen, char *key, size_t keylen, HashTable *parent, zval *zv, int (*callback)(phpdbg_watchpoint_t *)) {
630 	int ret;
631 	phpdbg_watchpoint_t *watch = ecalloc(1, sizeof(phpdbg_watchpoint_t));
632 	watch->str = zend_string_init(name, namelen, 0);
633 	watch->name_in_parent = zend_string_init(key, keylen, 0);
634 	watch->parent_container = parent;
635 	phpdbg_create_zval_watchpoint(zv, watch);
636 
637 	ret = callback(watch);
638 
639 	efree(name);
640 	efree(key);
641 
642 	if (ret != SUCCESS) {
643 		phpdbg_free_watch(watch);
644 		efree(watch);
645 	}
646 
647 	PHPDBG_G(watch_tmp) = NULL;
648 
649 	return ret;
650 }
651 
phpdbg_watchpoint_parse_input(char * input,size_t len,HashTable * parent,size_t i,int (* callback)(phpdbg_watchpoint_t *),zend_bool silent)652 PHPDBG_API int phpdbg_watchpoint_parse_input(char *input, size_t len, HashTable *parent, size_t i, int (*callback)(phpdbg_watchpoint_t *), zend_bool silent) {
653 	return phpdbg_parse_variable_with_arg(input, len, parent, i, (phpdbg_parse_var_with_arg_func) phpdbg_watchpoint_parse_wrapper, NULL, 0, callback);
654 }
655 
phpdbg_watchpoint_parse_step(char * name,size_t namelen,char * key,size_t keylen,HashTable * parent,zval * zv,int (* callback)(phpdbg_watchpoint_t *))656 static int phpdbg_watchpoint_parse_step(char *name, size_t namelen, char *key, size_t keylen, HashTable *parent, zval *zv, int (*callback)(phpdbg_watchpoint_t *)) {
657 	phpdbg_watchpoint_t *watch;
658 
659 	if ((watch = zend_hash_str_find_ptr(&PHPDBG_G(watchpoints), name, namelen))) {
660 		watch->flags |= PHPDBG_WATCH_IMPLICIT;
661 		PHPDBG_G(watch_tmp) = watch;
662 		return SUCCESS;
663 	}
664 
665 	watch = ecalloc(1, sizeof(phpdbg_watchpoint_t));
666 	watch->flags = PHPDBG_WATCH_IMPLICIT;
667 	watch->str = zend_string_init(name, namelen, 0);
668 	watch->name_in_parent = zend_string_init(key, keylen, 0);
669 	watch->parent_container = parent;
670 	watch->parent = PHPDBG_G(watch_tmp);
671 	phpdbg_create_zval_watchpoint(zv, watch);
672 
673 	phpdbg_create_watchpoint(watch);
674 
675 	efree(name);
676 	efree(key);
677 
678 	PHPDBG_G(watch_tmp) = watch;
679 	return SUCCESS;
680 }
681 
phpdbg_watchpoint_parse_symtables(char * input,size_t len,int (* callback)(phpdbg_watchpoint_t *))682 static int phpdbg_watchpoint_parse_symtables(char *input, size_t len, int (*callback)(phpdbg_watchpoint_t *)) {
683 	if (EG(scope) && len >= 5 && !memcmp("$this", input, 5)) {
684 		zend_hash_str_add(EG(current_execute_data)->symbol_table, ZEND_STRL("this"), &EG(current_execute_data)->This);
685 	}
686 
687 	if (phpdbg_is_auto_global(input, len) && phpdbg_watchpoint_parse_input(input, len, &EG(symbol_table), 0, callback, 1) != FAILURE) {
688 		return SUCCESS;
689 	}
690 
691 	return phpdbg_parse_variable_with_arg(input, len, EG(current_execute_data)->symbol_table, 0, (phpdbg_parse_var_with_arg_func) phpdbg_watchpoint_parse_wrapper, (phpdbg_parse_var_with_arg_func) phpdbg_watchpoint_parse_step, 0, callback);
692 }
693 
PHPDBG_WATCH(delete)694 PHPDBG_WATCH(delete) /* {{{ */
695 {
696 	switch (param->type) {
697 		case STR_PARAM:
698 			if (phpdbg_delete_var_watchpoint(param->str, param->len) == FAILURE) {
699 				phpdbg_error("watchdelete", "type=\"nowatch\"", "Nothing was deleted, no corresponding watchpoint found");
700 			} else {
701 				phpdbg_notice("watchdelete", "variable=\"%.*s\"", "Removed watchpoint %.*s", (int) param->len, param->str);
702 			}
703 			break;
704 
705 		phpdbg_default_switch_case();
706 	}
707 
708 	return SUCCESS;
709 } /* }}} */
710 
PHPDBG_WATCH(recursive)711 PHPDBG_WATCH(recursive) /* {{{ */
712 {
713 	if (phpdbg_rebuild_symtable() == FAILURE) {
714 		return SUCCESS;
715 	}
716 
717 	switch (param->type) {
718 		case STR_PARAM:
719 			if (phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_recursive_watchpoint) != FAILURE) {
720 				phpdbg_notice("watchrecursive", "variable=\"%.*s\"", "Set recursive watchpoint on %.*s", (int)param->len, param->str);
721 			}
722 			break;
723 
724 		phpdbg_default_switch_case();
725 	}
726 
727 	return SUCCESS;
728 } /* }}} */
729 
PHPDBG_WATCH(array)730 PHPDBG_WATCH(array) /* {{{ */
731 {
732 	if (phpdbg_rebuild_symtable() == FAILURE) {
733 		return SUCCESS;
734 	}
735 
736 	switch (param->type) {
737 		case STR_PARAM:
738 			if (phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_array_watchpoint) != FAILURE) {
739 				phpdbg_notice("watcharray", "variable=\"%.*s\"", "Set array watchpoint on %.*s", (int)param->len, param->str);
740 			}
741 			break;
742 
743 		phpdbg_default_switch_case();
744 	}
745 
746 	return SUCCESS;
747 } /* }}} */
748 
phpdbg_watch_HashTable_dtor(zval * zv)749 void phpdbg_watch_HashTable_dtor(zval *zv) {
750 	phpdbg_btree_result *result;
751 	zval *orig_zv = zv;
752 
753 	while (Z_TYPE_P(zv) == IS_INDIRECT) {
754 		zv = Z_INDIRECT_P(zv);
755 	}
756 
757 	if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) zv))) {
758 		phpdbg_watchpoint_t *watch = result->ptr;
759 
760 		if (watch->flags & PHPDBG_WATCH_NORMAL) {
761 			PHPDBG_G(watchpoint_hit) = 1;
762 
763 			phpdbg_notice("watchdelete", "variable=\"%.*s\" recursive=\"%s\"", "%.*s was removed, removing watchpoint%s", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str), (watch->flags & PHPDBG_WATCH_RECURSIVE) ? " recursively" : "");
764 		}
765 
766 		if ((result = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) watch->parent_container))) {
767 			phpdbg_watch_ht_info *hti = result->ptr;
768 			hti->dtor(orig_zv);
769 			zend_hash_del(&hti->watches, watch->name_in_parent);
770 			if (zend_hash_num_elements(&hti->watches) == 0) {
771 				watch->parent_container->pDestructor = hti->dtor;
772 				zend_hash_destroy(&hti->watches);
773 				phpdbg_btree_delete(&PHPDBG_G(watch_HashTables), (zend_ulong) watch->parent_container);
774 				efree(hti);
775 			}
776 		} else {
777 			zval_ptr_dtor_wrapper(orig_zv);
778 		}
779 
780 		if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
781 			phpdbg_delete_watchpoint_recursive(watch, 0);
782 		} else {
783 			zend_hash_del(&PHPDBG_G(watchpoints), watch->str);
784 		}
785 	}
786 }
787 
phpdbg_create_var_watchpoint(char * input,size_t len)788 int phpdbg_create_var_watchpoint(char *input, size_t len) {
789 	if (phpdbg_rebuild_symtable() == FAILURE) {
790 		return FAILURE;
791 	}
792 
793 	return phpdbg_watchpoint_parse_symtables(input, len, phpdbg_create_simple_watchpoint);
794 }
795 
phpdbg_delete_var_watchpoint(char * input,size_t len)796 int phpdbg_delete_var_watchpoint(char *input, size_t len) {
797 	if (phpdbg_rebuild_symtable() == FAILURE) {
798 		return FAILURE;
799 	}
800 
801 	return phpdbg_watchpoint_parse_input(input, len, EG(current_execute_data)->symbol_table, 0, phpdbg_delete_watchpoint, 0);
802 }
803 
804 #ifdef _WIN32
phpdbg_watchpoint_segfault_handler(void * addr)805 int phpdbg_watchpoint_segfault_handler(void *addr) {
806 #else
807 int phpdbg_watchpoint_segfault_handler(siginfo_t *info, void *context) {
808 #endif
809 	void *page;
810 	phpdbg_watch_memdump *dump;
811 	phpdbg_watchpoint_t *watch;
812 	size_t size;
813 
814 	watch = phpdbg_check_for_watchpoint(
815 #ifdef _WIN32
816 		addr
817 #else
818 		info->si_addr
819 #endif
820 		);
821 
822 	if (watch == NULL) {
823 		return FAILURE;
824 	}
825 
826 	page = phpdbg_get_page_boundary(watch->addr.ptr);
827 	size = phpdbg_get_total_page_size(watch->addr.ptr, watch->size);
828 
829 	/* re-enable writing */
830 	mprotect(page, size, PROT_READ | PROT_WRITE);
831 
832 	dump = malloc(MEMDUMP_SIZE(size));
833 	dump->page = page;
834 	dump->size = size;
835 	dump->reenable_writing = 0;
836 
837 	memcpy(&dump->data, page, size);
838 
839 	zend_llist_add_element(&PHPDBG_G(watchlist_mem), &dump);
840 
841 	return SUCCESS;
842 }
843 
844 void phpdbg_watchpoints_clean(void) {
845 	zend_hash_clean(&PHPDBG_G(watchpoints));
846 }
847 
848 /* due to implicit delete... MUST BE DESTROYED MANUALLY */
849 static void phpdbg_watch_dtor(zval *pDest) {
850 	phpdbg_watchpoint_t *watch = (phpdbg_watchpoint_t *) Z_PTR_P(pDest);
851 
852 	if (watch->flags & PHPDBG_WATCH_IMPLICIT) {
853 		watch->flags = PHPDBG_WATCH_SIMPLE; // tiny hack for delete_implicit_parents
854 
855 		if (watch->type == WATCH_ON_ZVAL) {
856 			phpdbg_delete_zval_watchpoints_recursive(watch);
857 		} else if (watch->type == WATCH_ON_HASHTABLE) {
858 			phpdbg_watchpoint_t *watchpoint;
859 
860 			watch->implicit_ht_count++;
861 
862 			ZEND_HASH_FOREACH_PTR(&((phpdbg_watch_ht_info *) phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) HT_WATCH_HT(watch))->ptr)->watches, watchpoint) {
863 				phpdbg_delete_watchpoint_recursive(watchpoint, 1);
864 			} ZEND_HASH_FOREACH_END();
865 		}
866 	}
867 
868 	phpdbg_delete_implicit_parents(watch);
869 
870 	phpdbg_deactivate_watchpoint(watch);
871 	phpdbg_remove_watchpoint(watch);
872 
873 	phpdbg_free_watch(watch);
874 	efree(watch);
875 }
876 
877 static void phpdbg_watch_mem_dtor(void *llist_data) {
878 	phpdbg_watch_memdump *dump = *(phpdbg_watch_memdump **) llist_data;
879 
880 	/* Disble writing again */
881 	if (dump->reenable_writing) {
882 		mprotect(dump->page, dump->size, PROT_READ);
883 	}
884 
885 	free(dump);
886 }
887 
888 static void phpdbg_watch_free_ptr_dtor(zval *ptr) {
889 	efree(Z_PTR_P(ptr));
890 }
891 
892 void phpdbg_setup_watchpoints(void) {
893 #if _SC_PAGE_SIZE
894 	phpdbg_pagesize = sysconf(_SC_PAGE_SIZE);
895 #elif _SC_PAGESIZE
896 	phpdbg_pagesize = sysconf(_SC_PAGESIZE);
897 #elif _SC_NUTC_OS_PAGESIZE
898 	phpdbg_pagesize = sysconf(_SC_NUTC_OS_PAGESIZE);
899 #else
900 	phpdbg_pagesize = 4096; /* common pagesize */
901 #endif
902 
903 	zend_llist_init(&PHPDBG_G(watchlist_mem), sizeof(void *), phpdbg_watch_mem_dtor, 1);
904 	phpdbg_btree_init(&PHPDBG_G(watchpoint_tree), sizeof(void *) * 8);
905 	phpdbg_btree_init(&PHPDBG_G(watch_HashTables), sizeof(void *) * 8);
906 	zend_hash_init(&PHPDBG_G(watchpoints), 8, NULL, phpdbg_watch_dtor, 0);
907 	zend_hash_init(&PHPDBG_G(watch_collisions), 8, NULL, phpdbg_watch_free_ptr_dtor, 0);
908 }
909 
910 static void phpdbg_print_changed_zval(phpdbg_watch_memdump *dump) {
911 	/* fetch all changes between dump->page and dump->page + dump->size */
912 	phpdbg_btree_position pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), (zend_ulong) dump->page, (zend_ulong) dump->page + dump->size);
913 	phpdbg_btree_result *result;
914 	int elementDiff;
915 	void *curTest;
916 
917 	dump->reenable_writing = 0;
918 
919 	while ((result = phpdbg_btree_next(&pos))) {
920 		phpdbg_watchpoint_t *watch = result->ptr;
921 		void *oldPtr = (char *) &dump->data + ((size_t) watch->addr.ptr - (size_t) dump->page);
922 		char reenable = 1;
923 		int removed = 0;
924 
925 		if ((size_t) watch->addr.ptr < (size_t) dump->page || (size_t) watch->addr.ptr + watch->size > (size_t) dump->page + dump->size) {
926 			continue;
927 		}
928 
929 		/* Test if the zval was separated or replaced and if necessary move the watchpoint */
930 		if ((watch->type == WATCH_ON_HASHTABLE || watch->type == WATCH_ON_ZVAL) && watch->parent_container) {
931 			if ((curTest = zend_symtable_find(watch->parent_container, watch->name_in_parent))) {
932 				while (Z_TYPE_P((zval *) curTest) == IS_INDIRECT) {
933 					curTest = Z_INDIRECT_P((zval *) curTest);
934 				}
935 
936 				if (watch->type == WATCH_ON_HASHTABLE) {
937 					switch (Z_TYPE_P((zval *) curTest)) {
938 						case IS_ARRAY:
939 							curTest = (void *) Z_ARRVAL_P((zval *) curTest);
940 							break;
941 						case IS_OBJECT:
942 							curTest = (void *) Z_OBJPROP_P((zval *) curTest);
943 							break;
944 					}
945 				}
946 
947 				if (curTest != watch->addr.ptr) {
948 					phpdbg_deactivate_watchpoint(watch);
949 					phpdbg_remove_watchpoint(watch);
950 					watch->addr.ptr = curTest;
951 					phpdbg_store_watchpoint(watch);
952 					phpdbg_activate_watchpoint(watch);
953 
954 					reenable = 0;
955 				}
956 			} else {
957 				removed = 1;
958 			}
959 		}
960 
961 		/* Show to the user what changed and delete watchpoint upon removal */
962 		{
963 			zend_bool do_break = 0;
964 
965 			switch (watch->type) {
966 				case WATCH_ON_ZVAL:
967 					do_break = memcmp(oldPtr, watch->addr.zv, sizeof(zend_value) + sizeof(uint32_t) /* value + typeinfo */) != 0;
968 					if (!do_break) {
969 						goto end;
970 					}
971 					break;
972 				case WATCH_ON_HASHTABLE:
973 					do_break = zend_hash_num_elements(HT_PTR_HT(oldPtr)) != zend_hash_num_elements(HT_WATCH_HT(watch));
974 					if (!do_break) {
975 						goto end;
976 					}
977 					break;
978 				case WATCH_ON_REFCOUNTED:
979 					do_break = memcmp(oldPtr, watch->addr.ref, sizeof(uint32_t) /* no zend_refcounted metadata info */) != 0;
980 					if (!do_break) {
981 						goto end;
982 					}
983 					if (!(PHPDBG_G(flags) & PHPDBG_SHOW_REFCOUNTS)) {
984 						do_break = 0;
985 					}
986 					break;
987 			}
988 
989 			if (!(watch->flags & PHPDBG_WATCH_NORMAL)) {
990 				do_break = 0;
991 			}
992 
993 			if (do_break) {
994 				PHPDBG_G(watchpoint_hit) = 1;
995 
996 				phpdbg_notice("watchhit", "variable=\"%s\"", "Breaking on watchpoint %.*s", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str));
997 				phpdbg_xml("<watchdata %r>");
998 			}
999 
1000 			switch (watch->type) {
1001 				case WATCH_ON_ZVAL: {
1002 					zend_bool show_value = memcmp(oldPtr, watch->addr.zv, sizeof(zend_value) + sizeof(uint32_t) /* no metadata info */) != 0;
1003 
1004 					if ((watch->flags & PHPDBG_WATCH_NORMAL) && (removed || show_value)) {
1005 /* TODO: Merge with refcounting watches, store if watched ref value is to be dropped etc. [for example: manually increment refcount transparently for displaying and drop it if it decrements to 1] */
1006 						if (Z_REFCOUNTED_P((zval *) oldPtr)) {
1007 							phpdbg_writeln("watchvalue", "type=\"old\" inaccessible=\"inaccessible\"", "Old value inaccessible or destroyed");
1008 						} else {
1009 							phpdbg_out("Old value: ");
1010 							phpdbg_xml("<watchvalue %r type=\"old\">");
1011 							zend_print_flat_zval_r((zval *) oldPtr);
1012 							phpdbg_xml("</watchvalue>");
1013 							phpdbg_out("\n");
1014 						}
1015 					}
1016 
1017 					/* check if zval was removed */
1018 					if (removed) {
1019 						if (watch->flags & PHPDBG_WATCH_NORMAL) {
1020 							phpdbg_notice("watchdelete", "variable=\"%.*s\"", "Watchpoint %.*s was unset, removing watchpoint", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str));
1021 						}
1022 						zend_hash_del(&PHPDBG_G(watchpoints), watch->str);
1023 
1024 						reenable = 0;
1025 
1026 						if (Z_REFCOUNTED_P((zval *) oldPtr)) {
1027 							phpdbg_remove_watch_collision(watch);
1028 						}
1029 						break;
1030 					}
1031 
1032 					if ((watch->flags & PHPDBG_WATCH_NORMAL) && show_value) {
1033 						phpdbg_out("New value%s: ", Z_ISREF_P(watch->addr.zv) ? " (reference)" : "");
1034 						phpdbg_xml("<watchvalue %r%s type=\"new\">", Z_ISREF_P(watch->addr.zv) ? " reference=\"reference\"" : "");
1035 						zend_print_flat_zval_r(watch->addr.zv);
1036 						phpdbg_xml("</watchvalue>");
1037 						phpdbg_out("\n");
1038 					}
1039 
1040 					/* add new watchpoints if necessary */
1041 					if (Z_PTR_P(watch->addr.zv) != Z_PTR_P((zval *) oldPtr) || Z_TYPE_P(watch->addr.zv) != Z_TYPE_P((zval *) oldPtr)) {
1042 						if (Z_REFCOUNTED_P((zval *) oldPtr)) {
1043 							zval *new_zv = watch->addr.zv;
1044 							watch->addr.ptr = oldPtr;
1045 							phpdbg_remove_watch_collision(watch);
1046 							watch->addr.zv = new_zv;
1047 						}
1048 						if (Z_REFCOUNTED_P(watch->addr.zv)) {
1049 							if ((watch->flags & PHPDBG_WATCH_NORMAL) && (PHPDBG_G(flags) & PHPDBG_SHOW_REFCOUNTS)) {
1050 								phpdbg_writeln("watchrefcount", "type=\"new\" refcount=\"%d\"", "New refcount: %d", Z_REFCOUNT_P(watch->addr.zv));
1051 							}
1052 							if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
1053 								phpdbg_create_recursive_watchpoint(watch);
1054 							} else if (Z_ISREF_P(watch->addr.zv)) {
1055 								phpdbg_create_reference_watch(watch);
1056 							}
1057 						}
1058 					}
1059 
1060 					break;
1061 				}
1062 				case WATCH_ON_HASHTABLE:
1063 					/* We should be safely able to assume the HashTable to be consistent (inconsistent HashTables should have been caught by phpdbg_watch_efree() */
1064 					elementDiff = zend_hash_num_elements(HT_PTR_HT(oldPtr)) - zend_hash_num_elements(HT_WATCH_HT(watch));
1065 					if ((watch->flags & PHPDBG_WATCH_NORMAL) && elementDiff) {
1066 						if (elementDiff > 0) {
1067 							phpdbg_writeln("watchsize", "removed=\"%d\"", "%d elements were removed from the array", elementDiff);
1068 						} else {
1069 							phpdbg_writeln("watchsize", "added=\"%d\"", "%d elements were added to the array", -elementDiff);
1070 						}
1071 					}
1072 					/* add new watchpoints if necessary */
1073 					if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
1074 						phpdbg_create_recursive_ht_watch(watch);
1075 					}
1076 					if ((watch->flags & PHPDBG_WATCH_NORMAL) && HT_WATCH_HT(watch)->nInternalPointer != HT_PTR_HT(oldPtr)->nInternalPointer) {
1077 						phpdbg_writeln("watcharrayptr", "", "Internal pointer of array was changed");
1078 					}
1079 					break;
1080 				case WATCH_ON_REFCOUNTED: {
1081 					if ((watch->flags & PHPDBG_WATCH_NORMAL) && (PHPDBG_G(flags) & PHPDBG_SHOW_REFCOUNTS)) {
1082 						phpdbg_writeln("watchrefcount", "type=\"old\" refcount=\"%d\"", "Old refcount: %d", GC_REFCOUNT((zend_refcounted *) oldPtr));
1083 						phpdbg_writeln("watchrefcount", "type=\"new\" refcount=\"%d\"", "New refcount: %d", GC_REFCOUNT(watch->addr.ref));
1084 					}
1085 					break;
1086 				}
1087 			}
1088 
1089 			if (do_break) {
1090 				phpdbg_xml("</watchdata>");
1091 			}
1092 		} end:
1093 
1094 		dump->reenable_writing = dump->reenable_writing | reenable;
1095 	}
1096 }
1097 
1098 int phpdbg_print_changed_zvals(void) {
1099 	zend_llist_position pos;
1100 	phpdbg_watch_memdump **dump;
1101 	int ret;
1102 
1103 	if (zend_llist_count(&PHPDBG_G(watchlist_mem)) == 0) {
1104 		return FAILURE;
1105 	}
1106 
1107 	dump = (phpdbg_watch_memdump **) zend_llist_get_last_ex(&PHPDBG_G(watchlist_mem), &pos);
1108 
1109 	do {
1110 		phpdbg_print_changed_zval(*dump);
1111 	} while ((dump = (phpdbg_watch_memdump **) zend_llist_get_prev_ex(&PHPDBG_G(watchlist_mem), &pos)));
1112 
1113 	zend_llist_clean(&PHPDBG_G(watchlist_mem));
1114 
1115 	ret = PHPDBG_G(watchpoint_hit) ? SUCCESS : FAILURE;
1116 	PHPDBG_G(watchpoint_hit) = 0;
1117 
1118 	return ret;
1119 }
1120 
1121 void phpdbg_list_watchpoints(void) {
1122 	phpdbg_watchpoint_t *watch;
1123 
1124 	phpdbg_xml("<watchlist %r>");
1125 
1126 	ZEND_HASH_FOREACH_PTR(&PHPDBG_G(watchpoints), watch) {
1127 		if (watch->flags & PHPDBG_WATCH_NORMAL) {
1128 			phpdbg_writeln("watchvariable", "variable=\"%.*s\" on=\"%s\" type=\"%s\"", "%.*s (%s, %s)", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str), watch->type == WATCH_ON_HASHTABLE ? "array" : watch->type == WATCH_ON_REFCOUNTED ? "refcount" : "variable", watch->flags == PHPDBG_WATCH_RECURSIVE ? "recursive" : "simple");
1129 		}
1130 	} ZEND_HASH_FOREACH_END();
1131 
1132 	phpdbg_xml("</watchlist>");
1133 }
1134 
1135 void phpdbg_watch_efree(void *ptr) {
1136 	phpdbg_btree_result *result;
1137 
1138 	result = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), (zend_ulong) ptr);
1139 
1140 	if (result) {
1141 		phpdbg_watchpoint_t *watch = result->ptr;
1142 
1143 		if ((size_t) watch->addr.ptr + watch->size > (size_t) ptr) {
1144 			if (watch->type == WATCH_ON_REFCOUNTED) {
1145 				/* remove watchpoint here from btree, zval watchpoint will remove it via remove_watch_collison */
1146 				phpdbg_deactivate_watchpoint(watch);
1147 				phpdbg_remove_watchpoint(watch);
1148 			} else {
1149 				if (watch->type == WATCH_ON_ZVAL) {
1150 					phpdbg_remove_watch_collision(watch);
1151 				}
1152 				if (watch->type == WATCH_ON_HASHTABLE && (watch->flags & PHPDBG_WATCH_SIMPLE)) {
1153 					/* when a HashTable is freed, we can safely assume the other zvals all were dtor'ed */
1154 					phpdbg_notice("watchdelete", "variable=\"%.*s\" recursive=\"%s\"", "Array %.*s was removed, removing watchpoint%s", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str), (watch->flags & PHPDBG_WATCH_RECURSIVE) ? " recursively" : "");
1155 				}
1156 				if (watch->type == WATCH_ON_HASHTABLE || watch->parent == NULL || watch->parent->type != WATCH_ON_ZVAL) { /* no references */
1157 					zend_hash_del(&PHPDBG_G(watchpoints), watch->str);
1158 				}
1159 			}
1160 		}
1161 	}
1162 
1163 	if (PHPDBG_G(original_free_function)) {
1164 		PHPDBG_G(original_free_function)(ptr);
1165 	}
1166 }
1167