xref: /PHP-8.2/sapi/phpdbg/phpdbg_watch.c (revision 03f0776d)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Authors: Felipe Pena <felipe@php.net>                                |
14    | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
15    | Authors: Bob Weinand <bwoebi@php.net>                                |
16    +----------------------------------------------------------------------+
17 */
18 
19 /* Some information for the reader...
20  *
21  * The main structure managing the direct observations is the watchpoint (phpdbg_watchpoint_t). There are several types of watchpoints currently:
22  * WATCH_ON_BUCKET: a watchpoint on a Bucket element, used to monitor values inside HashTables (largely handled equivalently to WATCH_ON_ZVAL, it just monitors also for IS_UNDEF and key changes)
23  * WATCH_ON_ZVAL: a watchpoint on a bare zval (&zend_reference.val, zval.value.indirect)
24  * WATCH_ON_STR: a watchpoint on a zend_string (put on &ZSTR_LEN() in order to not watch refcount/hash)
25  * WATCH_ON_HASHTABLE: a watchpoint on a HashTable (currently only used to observe size changes, put after flags in order to not watch refcount)
26  * WATCH_ON_REFCOUNTED: a watchpoint on a zend_refcounted, observes the refcount and serves as reference pointer in the custom efree handler
27  * WATCH_ON_HASHDATA: special watchpoint to watch for HT_GET_DATA_ADDR(ht) being efree()'d to be able to properly relocate Bucket watches
28  *
29  * Watch elements are either simple, recursive or implicit (PHPDBG_WATCH_* flags)
30  * Simple means that a particular watchpoint was explicitly defined
31  * Recursive watch elements are created recursively (recursive root flag is to distinguish the root element easily from its children recursive elements)
32  * Implicit  watch elements are implicitly created on all ancestors of simple or recursive watch elements
33  * Recursive and (simple or implicit) watch elements are mutually exclusive
34  * Array/Object to distinguish watch elements on arrays
35  *
36  * Watch elements all contain a reference to a watchpoint (except if scheduled for recreation); a "watch" is a watch element created by the user with a specific id
37  * Each watch has its independent structure of watch elements, watchpoints are responsible for managing collisions and preventing pointers being watched multiple times
38  *
39  * PHPDBG_G(watchpoint_tree) contains all watchpoints identified by the watch target address
40  * PHPDBG_G(watch_HashTables) contains the addresses of parent_containers of watch elements
41  * PHPDBG_G(watch_elements) contains all directly defined watch elements (i.e. those which have an individual id)
42  * PHPDBG_G(watch_collisions) is indexed by a zend_refcounted * pointer (phpdbg_watchpoint_t.ref). It stores information about collisions (everything which contains a zend_refcounted * may be referenced by multiple watches)
43  * PHPDBG_G(watch_free) is a set of pointers to watch for being freed (like HashTables referenced by phpdbg_watch_element.parent_container)
44  * PHPDBG_G(watch_recreation) is the list of watch elements whose watchpoint has been removed (via efree() for example) and needs to be recreated
45  * PHPDBG_G(watchlist_mem) is the list of unprotected memory pages; used to watch which pages need their PROT_WRITE attribute removed after checking
46  *
47  * Watching on addresses:
48  * * Address and size are transformed into memory page aligned address and size
49  * * mprotect() enables or disables them (depending on flags) - Windows has a transparent compatibility layer in phpdbg_win.c
50  * * segfault handler stores the address of the page and marks it again as writable
51  * * later watchpoints pointing inside these pages are compared against their current value and eventually reactivated (or deleted)
52  *
53  * Creating a watch:
54  * * Implicit watch elements for each element in the hierarchy (starting from base, which typically is current symbol table) except the last one
55  * * Create a watch element with either simple flag or recursive [+ root] flags
56  * * If the element has recursive flag, create elements recursively for every referenced HashTable and zval
57  *
58  * Creating a watch element:
59  * * For each watch element a related watchpoint is created, if there's none yet; add itself then into the list of parents of that watchpoint
60  * * If the watch has a parent_container, add itself also into a phpdbg_watch_ht_info (inside PHPDBG_G(watch_HashTables)) [and creates it if not yet existing]
61  *
62  * Creation of watchpoints:
63  * * Watchpoints create a watch collision for each refcounted or indirect on the zval (if type is WATCH_ON_BUCKET or WATCH_ON_ZVAL)
64  * * Backs the current value of the watched pointer up
65  * * Installs the watchpoint in PHPDBG_G(watchpoint_tree) and activates it (activation of a watchpoint = remove PROT_WRITE from the pages the watched pointer resides on)
66  *
67  * Watch collisions:
68  * * Manages a watchpoint on the refcount (WATCH_ON_REFCOUNTED) or indirect zval (WATCH_ON_ZVAL)
69  * * Guarantees that every pointer is watched at most once (by having a pointer to collision mapping in PHPDBG_G(watch_collisions), which have the unique watchpoints for the respective collision)
70  * * Contains a list of parents, i.e. which watchpoints reference it (via watch->ref)
71  * * If no watchpoint is referencing it anymore, the watch collision and its associated watchpoints (phpdbg_watch_collision.ref/reference) are removed
72  *
73  * Deleting a watch:
74  * * Watches are stored by an id in PHPDBG_G(watch_elements); the associated watch element is then deleted
75  * * Deletes all parent and children implicit watch elements
76  *
77  * Deleting a watch element:
78  * * Removes itself from the parent list of the associated watchpoints; if that parent list is empty, also delete the watchpoint
79  * * Removes itself from the related phpdbg_watch_ht_info if it has a parent_container
80  *
81  * Deleting a watchpoint:
82  * * Remove itself from watch collisions this watchpoint participates in
83  * * Removes the watchpoint from PHPDBG_G(watchpoint_tree) and deactivates it (deactivation of a watchpoint = add PROT_WRITE to the pages the watched pointer resides on)
84  *
85  * A watched pointer is efree()'d:
86  * * Needs immediate action as we else may run into dereferencing a pointer into freed memory
87  * * Deletes the associated watchpoint, and for each watch element, if recursive, all its children elements
88  * * If the its watch elements are implicit, recursive roots or simple, they and all their children are dissociated from their watchpoints (i.e. removed from the watchpoint, if no other element is referencing it, it is deleted); adds these elements to PHPDBG_G(watch_recreation)
89  *
90  * Recreating watchpoints:
91  * * Upon each opcode, PHPDBG_G(watch_recreation) is checked and all its elements are searched for whether the watch is still reachable via the tree given by its implicits
92  * * In case they are not reachable, the watch is deleted (and thus all the related watch elements), else a new watchpoint is created for all the watch elements
93  * * The old and new values of the watches are compared and shown if changed
94  *
95  * Comparing watchpoints:
96  * * The old and new values of the watches are compared and shown if changed
97  * * If changed, it is checked whether the refcounted/indirect changed and watch collisions removed or created accordingly
98  * * If a zval/bucket watchpoint is recursive, watch elements are added or removed accordingly
99  * * If an array watchpoint is recursive, new array watchpoints are added if there are new ones in the array
100  * * If the watch (element with an id) is not reachable anymore due to changes in implicits, the watch is removed
101  */
102 
103 #include "zend.h"
104 #include "phpdbg.h"
105 #include "phpdbg_btree.h"
106 #include "phpdbg_watch.h"
107 #include "phpdbg_utils.h"
108 #include "phpdbg_prompt.h"
109 #ifndef _WIN32
110 # include <unistd.h>
111 # include <sys/mman.h>
112 #endif
113 
114 #ifdef HAVE_USERFAULTFD_WRITEFAULT
115 # include <pthread.h>
116 # include <linux/userfaultfd.h>
117 # include <sys/ioctl.h>
118 # include <sys/syscall.h>
119 #endif
120 
121 ZEND_EXTERN_MODULE_GLOBALS(phpdbg)
122 
123 const phpdbg_command_t phpdbg_watch_commands[] = {
124 	PHPDBG_COMMAND_D_EX(array,      "create watchpoint on an array", 'a', watch_array,     &phpdbg_prompt_commands[24], "s", 0),
125 	PHPDBG_COMMAND_D_EX(delete,     "delete watchpoint",             'd', watch_delete,    &phpdbg_prompt_commands[24], "n", 0),
126 	PHPDBG_COMMAND_D_EX(recursive,  "create recursive watchpoints",  'r', watch_recursive, &phpdbg_prompt_commands[24], "s", 0),
127 	PHPDBG_END_COMMAND
128 };
129 
130 #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)
131 
132 #define HT_WATCH_OFFSET (sizeof(zend_refcounted *) + sizeof(uint32_t)) /* we are not interested in gc and flags */
133 #define HT_PTR_HT(ptr) ((HashTable *) (((char *) (ptr)) - HT_WATCH_OFFSET))
134 #define HT_WATCH_HT(watch) HT_PTR_HT((watch)->addr.ptr)
135 
136 /* ### PRINTING POINTER DIFFERENCES ### */
phpdbg_check_watch_diff(phpdbg_watchtype type,void * oldPtr,void * newPtr)137 bool phpdbg_check_watch_diff(phpdbg_watchtype type, void *oldPtr, void *newPtr) {
138 	switch (type) {
139 		case WATCH_ON_BUCKET:
140 			if (memcmp(&((Bucket *) oldPtr)->h, &((Bucket *) newPtr)->h, sizeof(Bucket) - sizeof(zval) /* key/val comparison */) != 0) {
141 				return 2;
142 			}
143 			/* TODO: Is this intentional? */
144 			ZEND_FALLTHROUGH;
145 		case WATCH_ON_ZVAL:
146 			return memcmp(oldPtr, newPtr, sizeof(zend_value) + sizeof(uint32_t) /* value + typeinfo */) != 0;
147 		case WATCH_ON_HASHTABLE:
148 			return zend_hash_num_elements(HT_PTR_HT(oldPtr)) != zend_hash_num_elements(HT_PTR_HT(newPtr));
149 		case WATCH_ON_REFCOUNTED:
150 			return memcmp(oldPtr, newPtr, sizeof(uint32_t) /* no zend_refcounted metadata info */) != 0;
151 		case WATCH_ON_STR:
152 			return memcmp(oldPtr, newPtr, *(size_t *) oldPtr + XtOffsetOf(zend_string, val) - XtOffsetOf(zend_string, len)) != 0;
153 		case WATCH_ON_HASHDATA:
154 			ZEND_UNREACHABLE();
155 	}
156 	return 0;
157 }
158 
phpdbg_print_watch_diff(phpdbg_watchtype type,zend_string * name,void * oldPtr,void * newPtr)159 void phpdbg_print_watch_diff(phpdbg_watchtype type, zend_string *name, void *oldPtr, void *newPtr) {
160 	int32_t elementDiff;
161 
162 	PHPDBG_G(watchpoint_hit) = 1;
163 
164 	phpdbg_notice("Breaking on watchpoint %.*s", (int) ZSTR_LEN(name), ZSTR_VAL(name));
165 
166 	switch (type) {
167 		case WATCH_ON_BUCKET:
168 		case WATCH_ON_ZVAL:
169 			if (Z_REFCOUNTED_P((zval *) oldPtr)) {
170 				phpdbg_writeln("Old value inaccessible or destroyed");
171 			} else if (Z_TYPE_P((zval *) oldPtr) == IS_INDIRECT) {
172 				phpdbg_writeln("Old value inaccessible or destroyed (was indirect)");
173 			} else {
174 				phpdbg_out("Old value: ");
175 				zend_print_flat_zval_r((zval *) oldPtr);
176 				phpdbg_out("\n");
177 			}
178 
179 			while (Z_TYPE_P((zval *) newPtr) == IS_INDIRECT) {
180 				newPtr = Z_INDIRECT_P((zval *) newPtr);
181 			}
182 
183 			phpdbg_out("New value%s: ", Z_ISREF_P((zval *) newPtr) ? " (reference)" : "");
184 			zend_print_flat_zval_r((zval *) newPtr);
185 			phpdbg_out("\n");
186 			break;
187 
188 		case WATCH_ON_HASHTABLE:
189 			elementDiff = zend_hash_num_elements(HT_PTR_HT(oldPtr)) - zend_hash_num_elements(HT_PTR_HT(newPtr));
190 			if (elementDiff > 0) {
191 				phpdbg_writeln("%d elements were removed from the array", (int) elementDiff);
192 			} else if (elementDiff < 0) {
193 				phpdbg_writeln("%d elements were added to the array", (int) -elementDiff);
194 			}
195 			break;
196 
197 		case WATCH_ON_REFCOUNTED:
198 			phpdbg_writeln("Old refcount: %d", GC_REFCOUNT((zend_refcounted *) oldPtr));
199 			phpdbg_writeln("New refcount: %d", GC_REFCOUNT((zend_refcounted *) newPtr));
200 			break;
201 
202 		case WATCH_ON_STR:
203 			phpdbg_out("Old value: ");
204 			zend_write((char *) oldPtr + XtOffsetOf(zend_string, val) - XtOffsetOf(zend_string, len), *(size_t *) oldPtr);
205 			phpdbg_out("\n");
206 
207 			phpdbg_out("New value: ");
208 			zend_write((char *) newPtr + XtOffsetOf(zend_string, val) - XtOffsetOf(zend_string, len), *(size_t *) newPtr);
209 			phpdbg_out("\n");
210 			break;
211 
212 		case WATCH_ON_HASHDATA:
213 			ZEND_UNREACHABLE();
214 	}
215 }
216 
217 /* ### LOW LEVEL WATCHPOINT HANDLING ### */
phpdbg_check_for_watchpoint(phpdbg_btree * tree,void * addr)218 static phpdbg_watchpoint_t *phpdbg_check_for_watchpoint(phpdbg_btree *tree, void *addr) {
219 	phpdbg_watchpoint_t *watch;
220 	phpdbg_btree_result *result = phpdbg_btree_find_closest(tree, (zend_ulong) phpdbg_get_page_boundary(addr) + phpdbg_pagesize - 1);
221 
222 	if (result == NULL) {
223 		return NULL;
224 	}
225 
226 	watch = result->ptr;
227 
228 	/* check if that addr is in a mprotect()'ed memory area */
229 	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) {
230 		/* failure */
231 		return NULL;
232 	}
233 
234 	return watch;
235 }
236 
phpdbg_change_watchpoint_access(phpdbg_watchpoint_t * watch,int access)237 static void phpdbg_change_watchpoint_access(phpdbg_watchpoint_t *watch, int access) {
238 	void *page_addr = phpdbg_get_page_boundary(watch->addr.ptr);
239 	size_t size = phpdbg_get_total_page_size(watch->addr.ptr, watch->size);
240 #ifdef HAVE_USERFAULTFD_WRITEFAULT
241 	if (PHPDBG_G(watch_userfaultfd)) {
242 		struct uffdio_range range = {
243 			.start = (__u64)(uintptr_t) page_addr,
244 			.len = size
245 		};
246 		if (access == PROT_READ) {
247 			struct uffdio_register reg = {
248 				.mode = UFFDIO_REGISTER_MODE_WP,
249 				.range = range
250 			};
251 			struct uffdio_writeprotect protect = {
252 				.mode = UFFDIO_WRITEPROTECT_MODE_WP,
253 				.range = range
254 			};
255 			ioctl(PHPDBG_G(watch_userfaultfd), UFFDIO_REGISTER,  &reg);
256 			ioctl(PHPDBG_G(watch_userfaultfd), UFFDIO_WRITEPROTECT,  &protect);
257 		} else {
258 			struct uffdio_register reg = {
259 				.mode = UFFDIO_REGISTER_MODE_WP,
260 				.range = range
261 			};
262 			ioctl(PHPDBG_G(watch_userfaultfd), UFFDIO_UNREGISTER,  &reg);
263 		}
264 	} else
265 #endif
266 	/* pagesize is assumed to be in the range of 2^x */
267 	{
268 		mprotect(page_addr, size, access);
269 	}
270 }
271 
phpdbg_activate_watchpoint(phpdbg_watchpoint_t * watch)272 static inline void phpdbg_activate_watchpoint(phpdbg_watchpoint_t *watch) {
273 	phpdbg_change_watchpoint_access(watch, PROT_READ);
274 }
275 
phpdbg_deactivate_watchpoint(phpdbg_watchpoint_t * watch)276 static inline void phpdbg_deactivate_watchpoint(phpdbg_watchpoint_t *watch) {
277 	phpdbg_change_watchpoint_access(watch, PROT_READ | PROT_WRITE);
278 }
279 
280 /* Note that consecutive pages need to be merged in order to avoid watchpoints spanning page boundaries to have part of their data in the one page, part in the other page */
281 #ifdef _WIN32
phpdbg_watchpoint_segfault_handler(void * addr)282 int phpdbg_watchpoint_segfault_handler(void *addr) {
283 #else
284 int phpdbg_watchpoint_segfault_handler(siginfo_t *info, void *context) {
285 #endif
286 
287 	void *page = phpdbg_get_page_boundary(
288 #ifdef _WIN32
289 		addr
290 #else
291 		info->si_addr
292 #endif
293 	);
294 
295 	/* perhaps unnecessary, but check to be sure to not conflict with other segfault handlers */
296 	if (phpdbg_check_for_watchpoint(&PHPDBG_G(watchpoint_tree), page) == NULL) {
297 		return FAILURE;
298 	}
299 
300 	/* re-enable writing */
301 	mprotect(page, phpdbg_pagesize, PROT_READ | PROT_WRITE);
302 
303 	zend_hash_index_add_empty_element(PHPDBG_G(watchlist_mem), (zend_ulong) page);
304 
305 	return SUCCESS;
306 }
307 
308 #ifdef HAVE_USERFAULTFD_WRITEFAULT
309 # if defined(__GNUC__) && !defined(__clang__)
310 __attribute__((no_sanitize_address))
311 # endif
312 void *phpdbg_watchpoint_userfaultfd_thread(void *phpdbg_globals) {
313 	pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
314 	zend_phpdbg_globals *globals = (zend_phpdbg_globals *) phpdbg_globals;
315 
316 	struct uffd_msg fault_msg = {0};
317 	while (read(globals->watch_userfaultfd, &fault_msg, sizeof(fault_msg)) == sizeof(fault_msg)) {
318 		void *page = phpdbg_get_page_boundary((char *)(uintptr_t) fault_msg.arg.pagefault.address);
319 		zend_hash_index_add_empty_element(globals->watchlist_mem, (zend_ulong) page);
320 		struct uffdio_writeprotect unprotect = {
321 			.mode = 0,
322 			.range = {
323 				.start = (__u64)(uintptr_t) page,
324 				.len = phpdbg_pagesize
325 			}
326 		};
327 		ioctl(globals->watch_userfaultfd, UFFDIO_WRITEPROTECT, &unprotect);
328 	}
329 
330 	return NULL;
331 }
332 #endif
333 
334 /* ### REGISTER WATCHPOINT ### To be used only by watch element and collision managers ### */
335 static inline void phpdbg_store_watchpoint_btree(phpdbg_watchpoint_t *watch) {
336 	phpdbg_btree_result *res;
337 	ZEND_ASSERT((res = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr)) == NULL || res->ptr == watch);
338 	phpdbg_btree_insert(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr, watch);
339 }
340 
341 static inline void phpdbg_remove_watchpoint_btree(phpdbg_watchpoint_t *watch) {
342 	phpdbg_btree_delete(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr);
343 }
344 
345 /* ### SET WATCHPOINT ADDR ### To be used only by watch element and collision managers ### */
346 void phpdbg_set_addr_watchpoint(void *addr, size_t size, phpdbg_watchpoint_t *watch) {
347 	watch->addr.ptr = addr;
348 	watch->size = size;
349 	watch->ref = NULL;
350 	watch->coll = NULL;
351 	zend_hash_init(&watch->elements, 8, brml, NULL, 0);
352 }
353 
354 void phpdbg_set_zval_watchpoint(zval *zv, phpdbg_watchpoint_t *watch) {
355 	phpdbg_set_addr_watchpoint(zv, sizeof(zval) - sizeof(uint32_t), watch);
356 	watch->type = WATCH_ON_ZVAL;
357 }
358 
359 void phpdbg_set_bucket_watchpoint(Bucket *bucket, phpdbg_watchpoint_t *watch) {
360 	phpdbg_set_addr_watchpoint(bucket, sizeof(Bucket), watch);
361 	watch->type = WATCH_ON_BUCKET;
362 }
363 
364 void phpdbg_set_ht_watchpoint(HashTable *ht, phpdbg_watchpoint_t *watch) {
365 	phpdbg_set_addr_watchpoint(((char *) ht) + HT_WATCH_OFFSET, sizeof(HashTable) - HT_WATCH_OFFSET, watch);
366 	watch->type = WATCH_ON_HASHTABLE;
367 }
368 
369 void phpdbg_watch_backup_data(phpdbg_watchpoint_t *watch) {
370 	switch (watch->type) {
371 		case WATCH_ON_BUCKET:
372 		case WATCH_ON_ZVAL:
373 		case WATCH_ON_REFCOUNTED:
374 			memcpy(&watch->backup, watch->addr.ptr, watch->size);
375 			break;
376 		case WATCH_ON_STR:
377 			if (watch->backup.str) {
378 				zend_string_release(watch->backup.str);
379 			}
380 			watch->backup.str = zend_string_init((char *) watch->addr.ptr + XtOffsetOf(zend_string, val) - XtOffsetOf(zend_string, len), *(size_t *) watch->addr.ptr, 1);
381 			GC_MAKE_PERSISTENT_LOCAL(watch->backup.str);
382 			break;
383 		case WATCH_ON_HASHTABLE:
384 			memcpy((char *) &watch->backup + HT_WATCH_OFFSET, watch->addr.ptr, watch->size);
385 		case WATCH_ON_HASHDATA:
386 			break;
387 	}
388 }
389 
390 /* ### MANAGE WATCH COLLISIONS ### To be used only by watch element manager and memory differ ### */
391 /* watch collisions are responsible for having only one watcher on a given refcounted/refval and having a mapping back to the parent zvals */
392 void phpdbg_delete_watch_collision(phpdbg_watchpoint_t *watch) {
393 	phpdbg_watch_collision *coll;
394 	if ((coll = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref))) {
395 		zend_hash_index_del(&coll->parents, (zend_ulong) watch);
396 		if (zend_hash_num_elements(&coll->parents) == 0) {
397 			phpdbg_remove_watchpoint_btree(&coll->ref);
398 			phpdbg_deactivate_watchpoint(&coll->ref);
399 
400 			if (coll->ref.type == WATCH_ON_ZVAL) {
401 				phpdbg_delete_watch_collision(&coll->ref);
402 			} else if (coll->reference.addr.ptr) {
403 				phpdbg_remove_watchpoint_btree(&coll->reference);
404 				phpdbg_deactivate_watchpoint(&coll->reference);
405 				phpdbg_delete_watch_collision(&coll->reference);
406 				if (coll->reference.type == WATCH_ON_STR) {
407 					zend_string_release(coll->reference.backup.str);
408 				}
409 			}
410 
411 			zend_hash_index_del(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref);
412 			zend_hash_destroy(&coll->parents);
413 			efree(coll);
414 		}
415 	}
416 }
417 
418 void phpdbg_update_watch_ref(phpdbg_watchpoint_t *watch) {
419 	phpdbg_watch_collision *coll;
420 
421 	ZEND_ASSERT(watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET);
422 	if (Z_REFCOUNTED_P(watch->addr.zv)) {
423 		if (Z_COUNTED_P(watch->addr.zv) == watch->ref) {
424 			return;
425 		}
426 
427 		if (watch->ref != NULL) {
428 			phpdbg_delete_watch_collision(watch);
429 		}
430 
431 		watch->ref = Z_COUNTED_P(watch->addr.zv);
432 
433 		if (!(coll = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref))) {
434 			coll = emalloc(sizeof(*coll));
435 			coll->ref.type = WATCH_ON_REFCOUNTED;
436 			phpdbg_set_addr_watchpoint(Z_COUNTED_P(watch->addr.zv), sizeof(uint32_t), &coll->ref);
437 			coll->ref.coll = coll;
438 			phpdbg_store_watchpoint_btree(&coll->ref);
439 			phpdbg_activate_watchpoint(&coll->ref);
440 			phpdbg_watch_backup_data(&coll->ref);
441 
442 			if (Z_ISREF_P(watch->addr.zv)) {
443 				phpdbg_set_zval_watchpoint(Z_REFVAL_P(watch->addr.zv), &coll->reference);
444 				coll->reference.coll = coll;
445 				phpdbg_update_watch_ref(&coll->reference);
446 				phpdbg_store_watchpoint_btree(&coll->reference);
447 				phpdbg_activate_watchpoint(&coll->reference);
448 				phpdbg_watch_backup_data(&coll->reference);
449 			} else if (Z_TYPE_P(watch->addr.zv) == IS_STRING) {
450 				coll->reference.type = WATCH_ON_STR;
451 				phpdbg_set_addr_watchpoint(&Z_STRLEN_P(watch->addr.zv), XtOffsetOf(zend_string, val) - XtOffsetOf(zend_string, len) + Z_STRLEN_P(watch->addr.zv) + 1, &coll->reference);
452 				coll->reference.coll = coll;
453 				phpdbg_store_watchpoint_btree(&coll->reference);
454 				phpdbg_activate_watchpoint(&coll->reference);
455 				coll->reference.backup.str = NULL;
456 				phpdbg_watch_backup_data(&coll->reference);
457 			} else {
458 				coll->reference.addr.ptr = NULL;
459 			}
460 
461 			zend_hash_init(&coll->parents, 8, shitty stupid parameter, NULL, 0);
462 			zend_hash_index_add_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref, coll);
463 		}
464 		zend_hash_index_add_ptr(&coll->parents, (zend_long) watch, watch);
465 	} else if (Z_TYPE_P(watch->addr.zv) == IS_INDIRECT) {
466 		if ((zend_refcounted *) Z_INDIRECT_P(watch->addr.zv) == watch->ref) {
467 			return;
468 		}
469 
470 		if (watch->ref != NULL) {
471 			phpdbg_delete_watch_collision(watch);
472 		}
473 
474 		watch->ref = (zend_refcounted *) Z_INDIRECT_P(watch->addr.zv);
475 
476 		if (!(coll = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref))) {
477 			coll = emalloc(sizeof(*coll));
478 			phpdbg_set_zval_watchpoint(Z_INDIRECT_P(watch->addr.zv), &coll->ref);
479 			coll->ref.coll = coll;
480 			phpdbg_update_watch_ref(&coll->ref);
481 			phpdbg_store_watchpoint_btree(&coll->ref);
482 			phpdbg_activate_watchpoint(&coll->ref);
483 			phpdbg_watch_backup_data(&coll->ref);
484 
485 			zend_hash_init(&coll->parents, 8, shitty stupid parameter, NULL, 0);
486 			zend_hash_index_add_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref, coll);
487 		}
488 		zend_hash_index_add_ptr(&coll->parents, (zend_long) watch, watch);
489 	} else if (watch->ref) {
490 		phpdbg_delete_watch_collision(watch);
491 		watch->ref = NULL;
492 	}
493 }
494 
495 /* ### MANAGE WATCH ELEMENTS ### */
496 /* watchpoints must be unique per element. Only one watchpoint may point to one element. But many elements may point to one watchpoint. */
497 void phpdbg_recurse_watch_element(phpdbg_watch_element *element);
498 void phpdbg_remove_watch_element_recursively(phpdbg_watch_element *element);
499 void phpdbg_free_watch_element(phpdbg_watch_element *element);
500 void phpdbg_remove_watchpoint(phpdbg_watchpoint_t *watch);
501 void phpdbg_watch_parent_ht(phpdbg_watch_element *element);
502 
503 phpdbg_watch_element *phpdbg_add_watch_element(phpdbg_watchpoint_t *watch, phpdbg_watch_element *element) {
504 	phpdbg_btree_result *res;
505 	if ((res = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr)) == NULL) {
506 		phpdbg_watchpoint_t *mem = emalloc(sizeof(*mem));
507 		*mem = *watch;
508 		watch = mem;
509 		phpdbg_store_watchpoint_btree(watch);
510 		if (watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) {
511 			phpdbg_update_watch_ref(watch);
512 		}
513 		phpdbg_activate_watchpoint(watch);
514 		phpdbg_watch_backup_data(watch);
515 	} else {
516 		phpdbg_watch_element *old_element;
517 		watch = res->ptr;
518 		if ((old_element = zend_hash_find_ptr(&watch->elements, element->str))) {
519 			phpdbg_free_watch_element(element);
520 			return old_element;
521 		}
522 	}
523 
524 	element->watch = watch;
525 	zend_hash_add_ptr(&watch->elements, element->str, element);
526 
527 	if (element->flags & PHPDBG_WATCH_RECURSIVE) {
528 		phpdbg_recurse_watch_element(element);
529 	}
530 
531 	return element;
532 }
533 
534 phpdbg_watch_element *phpdbg_add_bucket_watch_element(Bucket *bucket, phpdbg_watch_element *element) {
535 	phpdbg_watchpoint_t watch;
536 	phpdbg_set_bucket_watchpoint(bucket, &watch);
537 	element = phpdbg_add_watch_element(&watch, element);
538 	phpdbg_watch_parent_ht(element);
539 	return element;
540 }
541 
542 phpdbg_watch_element *phpdbg_add_ht_watch_element(zval *zv, phpdbg_watch_element *element) {
543 	phpdbg_watchpoint_t watch;
544 	HashTable *ht = HT_FROM_ZVP(zv);
545 
546 	if (!ht) {
547 		return NULL;
548 	}
549 
550 	element->flags |= Z_TYPE_P(zv) == IS_ARRAY ? PHPDBG_WATCH_ARRAY : PHPDBG_WATCH_OBJECT;
551 	phpdbg_set_ht_watchpoint(ht, &watch);
552 	return phpdbg_add_watch_element(&watch, element);
553 }
554 
555 bool phpdbg_is_recursively_watched(void *ptr, phpdbg_watch_element *element) {
556 	phpdbg_watch_element *next = element;
557 	do {
558 		element = next;
559 		if (element->watch->addr.ptr == ptr) {
560 			return 1;
561 		}
562 		next = element->parent;
563 	} while (!(element->flags & PHPDBG_WATCH_RECURSIVE_ROOT));
564 
565 	return 0;
566 }
567 
568 void phpdbg_add_recursive_watch_from_ht(phpdbg_watch_element *element, zend_long idx, zend_string *str, zval *zv) {
569 	phpdbg_watch_element *child;
570 	if (phpdbg_is_recursively_watched(zv, element)) {
571 		return;
572 	}
573 
574 	child = emalloc(sizeof(*child));
575 	child->flags = PHPDBG_WATCH_RECURSIVE;
576 	if (str) {
577 		child->str = strpprintf(0, (element->flags & PHPDBG_WATCH_ARRAY) ? "%.*s[%s]" : "%.*s->%s", (int) ZSTR_LEN(element->str) - 2, ZSTR_VAL(element->str), phpdbg_get_property_key(ZSTR_VAL(str)));
578 	} else {
579 		child->str = strpprintf(0, (element->flags & PHPDBG_WATCH_ARRAY) ? "%.*s[" ZEND_LONG_FMT "]" : "%.*s->" ZEND_LONG_FMT, (int) ZSTR_LEN(element->str) - 2, ZSTR_VAL(element->str), idx);
580 	}
581 	if (!str) {
582 		str = zend_long_to_str(idx); // TODO: hack, use proper int handling for name in parent
583 	} else { str = zend_string_copy(str); }
584 	child->name_in_parent = str;
585 	child->parent = element;
586 	child->child = NULL;
587 	child->parent_container = HT_WATCH_HT(element->watch);
588 	zend_hash_add_ptr(&element->child_container, child->str, child);
589 	phpdbg_add_bucket_watch_element((Bucket *) zv, child);
590 }
591 
592 void phpdbg_recurse_watch_element(phpdbg_watch_element *element) {
593 	phpdbg_watch_element *child;
594 	zval *zv;
595 
596 	if (element->watch->type == WATCH_ON_ZVAL || element->watch->type == WATCH_ON_BUCKET) {
597 		zv = element->watch->addr.zv;
598 		while (Z_TYPE_P(zv) == IS_INDIRECT) {
599 			zv = Z_INDIRECT_P(zv);
600 		}
601 		ZVAL_DEREF(zv);
602 
603 		if (element->child) {
604 			phpdbg_remove_watch_element_recursively(element->child);
605 		}
606 
607 		if ((Z_TYPE_P(zv) != IS_ARRAY && Z_TYPE_P(zv) != IS_OBJECT)
608 		    || phpdbg_is_recursively_watched(HT_WATCH_OFFSET + (char *) HT_FROM_ZVP(zv), element)) {
609 			if (element->child) {
610 				phpdbg_free_watch_element(element->child);
611 				element->child = NULL;
612 			}
613 			return;
614 		}
615 
616 		if (element->child) {
617 			child = element->child;
618 		} else {
619 			child = emalloc(sizeof(*child));
620 			child->flags = PHPDBG_WATCH_RECURSIVE;
621 			child->str = strpprintf(0, "%.*s[]", (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str));
622 			child->name_in_parent = NULL;
623 			child->parent = element;
624 			child->child = NULL;
625 			element->child = child;
626 		}
627 		zend_hash_init(&child->child_container, 8, NULL, NULL, 0);
628 		phpdbg_add_ht_watch_element(zv, child);
629 	} else if (zend_hash_num_elements(&element->child_container) == 0) {
630 		zend_string *str;
631 		zend_long idx;
632 
633 		ZEND_ASSERT(element->watch->type == WATCH_ON_HASHTABLE);
634 		ZEND_HASH_FOREACH_KEY_VAL(HT_WATCH_HT(element->watch), idx, str, zv) {
635 			phpdbg_add_recursive_watch_from_ht(element, idx, str, zv);
636 		} ZEND_HASH_FOREACH_END();
637 	}
638 }
639 
640 void phpdbg_watch_parent_ht(phpdbg_watch_element *element) {
641 	if (element->watch->type == WATCH_ON_BUCKET) {
642 		phpdbg_btree_result *res;
643 		HashPosition pos;
644 		phpdbg_watch_ht_info *hti;
645 		ZEND_ASSERT(element->parent_container);
646 		if (!(res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) element->parent_container))) {
647 			hti = emalloc(sizeof(*hti));
648 			hti->ht = element->parent_container;
649 
650 			zend_hash_init(&hti->watches, 0, grrrrr, ZVAL_PTR_DTOR, 0);
651 			phpdbg_btree_insert(&PHPDBG_G(watch_HashTables), (zend_ulong) hti->ht, hti);
652 
653 			phpdbg_set_addr_watchpoint(HT_GET_DATA_ADDR(hti->ht), HT_HASH_SIZE(hti->ht->nTableMask), &hti->hash_watch);
654 			hti->hash_watch.type = WATCH_ON_HASHDATA;
655 			phpdbg_store_watchpoint_btree(&hti->hash_watch);
656 			phpdbg_activate_watchpoint(&hti->hash_watch);
657 		} else {
658 			hti = (phpdbg_watch_ht_info *) res->ptr;
659 		}
660 
661 		zend_hash_internal_pointer_end_ex(hti->ht, &pos);
662 		hti->last = hti->ht->arData + pos;
663 		hti->last_str = hti->last->key;
664 		hti->last_idx = hti->last->h;
665 
666 		zend_hash_add_ptr(&hti->watches, element->name_in_parent, element);
667 	}
668 }
669 
670 void phpdbg_unwatch_parent_ht(phpdbg_watch_element *element) {
671 	if (element->watch && element->watch->type == WATCH_ON_BUCKET) {
672 		phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) element->parent_container);
673 		ZEND_ASSERT(element->parent_container);
674 		if (res) {
675 			phpdbg_watch_ht_info *hti = res->ptr;
676 
677 			if (zend_hash_num_elements(&hti->watches) == 1) {
678 				zend_hash_destroy(&hti->watches);
679 				phpdbg_btree_delete(&PHPDBG_G(watch_HashTables), (zend_ulong) hti->ht);
680 				phpdbg_remove_watchpoint_btree(&hti->hash_watch);
681 				phpdbg_deactivate_watchpoint(&hti->hash_watch);
682 				efree(hti);
683 			} else {
684 				zend_hash_del(&hti->watches, element->name_in_parent);
685 			}
686 		}
687 	}
688 }
689 
690 /* ### DE/QUEUE WATCH ELEMENTS ### to be used by watch element manager only */
691 /* implicit watchpoints may change (especially because of separation); elements updated by remove & re-add etc.; thus we need to wait a little bit (until next opcode) and then compare whether the watchpoint still exists and if not, remove it */
692 
693 void phpdbg_dissociate_watch_element(phpdbg_watch_element *element, phpdbg_watch_element *until);
694 void phpdbg_free_watch_element_tree(phpdbg_watch_element *element);
695 
696 void phpdbg_queue_element_for_recreation(phpdbg_watch_element *element) {
697 	/* store lowermost element */
698 	phpdbg_watch_element *prev;
699 
700 	if ((prev = zend_hash_find_ptr(&PHPDBG_G(watch_recreation), element->str))) {
701 		phpdbg_watch_element *child = prev;
702 		do {
703 			if (child == element) {
704 				return;
705 			}
706 			child = child->child;
707 		} while (child);
708 	}
709 	zend_hash_update_ptr(&PHPDBG_G(watch_recreation), element->str, element);
710 
711 	/* dissociate from watchpoint to avoid dangling memory watches */
712 	phpdbg_dissociate_watch_element(element, prev);
713 
714 	if (!element->parent) {
715 		/* HERE BE DRAGONS; i.e. we assume HashTable is directly allocated via emalloc() ... (which *should be* the case for every user-accessible array and symbol tables) */
716 		zend_hash_index_add_empty_element(&PHPDBG_G(watch_free), (zend_ulong) element->parent_container);
717 	}
718 }
719 
720 bool phpdbg_try_re_adding_watch_element(zval *parent, phpdbg_watch_element *element) {
721 	zval *zv;
722 	HashTable *ht = HT_FROM_ZVP(parent);
723 
724 	if (!ht) {
725 		return 0;
726 	} else if (element->flags & (PHPDBG_WATCH_ARRAY | PHPDBG_WATCH_OBJECT)) {
727 		char *htPtr = ((char *) ht) + HT_WATCH_OFFSET;
728 		char *oldPtr = ((char *) &element->backup.ht) + HT_WATCH_OFFSET;
729 		if (phpdbg_check_watch_diff(WATCH_ON_HASHTABLE, oldPtr, htPtr)) {
730 			phpdbg_print_watch_diff(WATCH_ON_HASHTABLE, element->str, oldPtr, htPtr);
731 		}
732 
733 		phpdbg_add_ht_watch_element(parent, element);
734 	} else if ((zv = zend_symtable_find(ht, element->name_in_parent))) {
735 		if (element->flags & PHPDBG_WATCH_IMPLICIT) {
736 			zval *next = zv;
737 
738 			while (Z_TYPE_P(next) == IS_INDIRECT) {
739 				next = Z_INDIRECT_P(next);
740 			}
741 			if (Z_ISREF_P(next)) {
742 				next = Z_REFVAL_P(next);
743 			}
744 
745 			if (!phpdbg_try_re_adding_watch_element(next, element->child)) {
746 				return 0;
747 			}
748 		} else if (phpdbg_check_watch_diff(WATCH_ON_ZVAL, &element->backup.zv, zv)) {
749 			phpdbg_print_watch_diff(WATCH_ON_ZVAL, element->str, &element->backup.zv, zv);
750 		}
751 
752 		element->parent_container = ht;
753 		phpdbg_add_bucket_watch_element((Bucket *) zv, element);
754 		phpdbg_watch_parent_ht(element);
755 	} else {
756 		return 0;
757 	}
758 
759 	return 1;
760 }
761 
762 void phpdbg_automatic_dequeue_free(phpdbg_watch_element *element) {
763 	phpdbg_watch_element *child = element;
764 	while (child->child && !(child->flags & PHPDBG_WATCH_RECURSIVE_ROOT)) {
765 		child = child->child;
766 	}
767 	PHPDBG_G(watchpoint_hit) = 1;
768 	if (zend_hash_index_del(&PHPDBG_G(watch_elements), child->id) == SUCCESS) {
769 		phpdbg_notice("%.*s has been removed, removing watchpoint%s", (int) ZSTR_LEN(child->str), ZSTR_VAL(child->str), (child->flags & PHPDBG_WATCH_RECURSIVE_ROOT) ? " recursively" : "");
770 	}
771 	phpdbg_free_watch_element_tree(element);
772 }
773 
774 void phpdbg_dequeue_elements_for_recreation(void) {
775 	phpdbg_watch_element *element;
776 
777 	ZEND_HASH_MAP_FOREACH_PTR(&PHPDBG_G(watch_recreation), element) {
778 		ZEND_ASSERT(element->flags & (PHPDBG_WATCH_IMPLICIT | PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_SIMPLE));
779 		if (element->parent || zend_hash_index_find(&PHPDBG_G(watch_free), (zend_ulong) element->parent_container)) {
780 			zval _zv, *zv = &_zv;
781 			if (element->parent) {
782 				ZEND_ASSERT(element->parent->watch->type == WATCH_ON_ZVAL || element->parent->watch->type == WATCH_ON_BUCKET);
783 				zv = element->parent->watch->addr.zv;
784 				while (Z_TYPE_P(zv) == IS_INDIRECT) {
785 					zv = Z_INDIRECT_P(zv);
786 				}
787 				ZVAL_DEREF(zv);
788 			} else {
789 				ZVAL_ARR(zv, element->parent_container);
790 			}
791 			if (!phpdbg_try_re_adding_watch_element(zv, element)) {
792 				phpdbg_automatic_dequeue_free(element);
793 			}
794 		} else {
795 			phpdbg_automatic_dequeue_free(element);
796 		}
797 	} ZEND_HASH_FOREACH_END();
798 
799 	zend_hash_clean(&PHPDBG_G(watch_recreation));
800 	zend_hash_clean(&PHPDBG_G(watch_free));
801 }
802 
803 /* ### WATCH ELEMENT DELETION ### only use phpdbg_remove_watch_element from the exterior */
804 void phpdbg_clean_watch_element(phpdbg_watch_element *element);
805 
806 void phpdbg_free_watch_element(phpdbg_watch_element *element) {
807 	zend_string_release(element->str);
808 	if (element->name_in_parent) {
809 		zend_string_release(element->name_in_parent);
810 	}
811 	efree(element);
812 }
813 
814 /* note: does *not* free the passed element, only clean */
815 void phpdbg_remove_watch_element_recursively(phpdbg_watch_element *element) {
816 	if (element->child) {
817 		phpdbg_remove_watch_element_recursively(element->child);
818 		phpdbg_free_watch_element(element->child);
819 		element->child = NULL;
820 	} else if (element->flags & (PHPDBG_WATCH_ARRAY | PHPDBG_WATCH_OBJECT)) {
821 		phpdbg_watch_element *child;
822 		ZEND_HASH_MAP_FOREACH_PTR(&element->child_container, child) {
823 			phpdbg_remove_watch_element_recursively(child);
824 			phpdbg_free_watch_element(child);
825 		} ZEND_HASH_FOREACH_END();
826 		zend_hash_destroy(&element->child_container);
827 	}
828 
829 	phpdbg_clean_watch_element(element);
830 }
831 
832 /* remove single watch (i.e. manual unset) or implicit removed */
833 void phpdbg_remove_watch_element(phpdbg_watch_element *element) {
834 	phpdbg_watch_element *parent = element->parent, *child = element->child;
835 	while (parent) {
836 		phpdbg_watch_element *cur = parent;
837 		parent = parent->parent;
838 		phpdbg_clean_watch_element(cur);
839 		phpdbg_free_watch_element(cur);
840 	}
841 	while (child) {
842 		phpdbg_watch_element *cur = child;
843 		child = child->child;
844 		if (cur->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
845 			phpdbg_remove_watch_element_recursively(cur);
846 			child = NULL;
847 		} else {
848 			phpdbg_clean_watch_element(cur);
849 		}
850 		phpdbg_free_watch_element(cur);
851 	}
852 	if (element->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
853 		phpdbg_remove_watch_element_recursively(element);
854 	} else {
855 		phpdbg_clean_watch_element(element);
856 	}
857 	zend_hash_index_del(&PHPDBG_G(watch_elements), element->id);
858 	phpdbg_free_watch_element(element);
859 }
860 
861 void phpdbg_backup_watch_element(phpdbg_watch_element *element) {
862 	memcpy(&element->backup, &element->watch->backup, /* element->watch->size */ sizeof(element->backup));
863 }
864 
865 /* until argument to prevent double remove of children elements */
866 void phpdbg_dissociate_watch_element(phpdbg_watch_element *element, phpdbg_watch_element *until) {
867 	phpdbg_watch_element *child = element;
868 	ZEND_ASSERT((element->flags & (PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_RECURSIVE)) != PHPDBG_WATCH_RECURSIVE);
869 
870 	if (element->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
871 		phpdbg_backup_watch_element(element);
872 		phpdbg_remove_watch_element_recursively(element);
873 		return;
874 	}
875 
876 	while (child->child != until) {
877 		child = child->child;
878 		if (child->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
879 			phpdbg_backup_watch_element(child);
880 			phpdbg_remove_watch_element_recursively(child);
881 			child->child = NULL;
882 			break;
883 		}
884 		if (child->child == NULL || (child->flags & PHPDBG_WATCH_RECURSIVE_ROOT)) {
885 			phpdbg_backup_watch_element(child);
886 		}
887 		phpdbg_clean_watch_element(child);
888 	}
889 	/* element needs to be removed last! */
890 	if (element->child == NULL) {
891 		phpdbg_backup_watch_element(element);
892 	}
893 	phpdbg_clean_watch_element(element);
894 }
895 
896 /* unlike phpdbg_remove_watch_element this *only* frees and does not clean up element + children! Only use after previous cleanup (e.g. phpdbg_dissociate_watch_element) */
897 void phpdbg_free_watch_element_tree(phpdbg_watch_element *element) {
898 	phpdbg_watch_element *parent = element->parent, *child = element->child;
899 	while (parent) {
900 		phpdbg_watch_element *cur = parent;
901 		parent = parent->parent;
902 		phpdbg_clean_watch_element(cur);
903 		phpdbg_free_watch_element(cur);
904 	}
905 	while (child) {
906 		phpdbg_watch_element *cur = child;
907 		child = child->child;
908 		phpdbg_free_watch_element(cur);
909 	}
910 	phpdbg_free_watch_element(element);
911 }
912 
913 void phpdbg_update_watch_element_watch(phpdbg_watch_element *element) {
914 	if (element->flags & PHPDBG_WATCH_IMPLICIT) {
915 		phpdbg_watch_element *child = element->child;
916 		while (child->flags & PHPDBG_WATCH_IMPLICIT) {
917 			child = child->child;
918 		}
919 
920 		ZEND_ASSERT(element->watch->type == WATCH_ON_ZVAL || element->watch->type == WATCH_ON_BUCKET);
921 		phpdbg_queue_element_for_recreation(element);
922 	} else if (element->flags & (PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_SIMPLE)) {
923 		phpdbg_queue_element_for_recreation(element);
924 	} else if (element->flags & PHPDBG_WATCH_RECURSIVE) {
925 		phpdbg_remove_watch_element_recursively(element);
926 		if (element->parent->flags & (PHPDBG_WATCH_OBJECT | PHPDBG_WATCH_ARRAY)) {
927 			zend_hash_del(&element->parent->child_container, element->str);
928 		} else {
929 			element->parent->child = NULL;
930 		}
931 		phpdbg_free_watch_element(element);
932 	}
933 }
934 
935 void phpdbg_update_watch_collision_elements(phpdbg_watchpoint_t *watch) {
936 	phpdbg_watchpoint_t *parent;
937 	phpdbg_watch_element *element;
938 
939 	ZEND_HASH_MAP_FOREACH_PTR(&watch->coll->parents, parent) {
940 		if (parent->coll) {
941 			phpdbg_update_watch_collision_elements(parent);
942 		} else {
943 			ZEND_HASH_MAP_FOREACH_PTR(&parent->elements, element) {
944 				phpdbg_update_watch_element_watch(element);
945 			} ZEND_HASH_FOREACH_END();
946 		}
947 	} ZEND_HASH_FOREACH_END();
948 }
949 
950 void phpdbg_remove_watchpoint(phpdbg_watchpoint_t *watch) {
951 	phpdbg_watch_element *element;
952 
953 	phpdbg_remove_watchpoint_btree(watch);
954 	phpdbg_deactivate_watchpoint(watch);
955 	phpdbg_delete_watch_collision(watch);
956 
957 	if (watch->coll) {
958 		phpdbg_update_watch_collision_elements(watch);
959 		return;
960 	}
961 
962 	watch->elements.nNumOfElements++; /* dirty hack to avoid double free */
963 	ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
964 		phpdbg_update_watch_element_watch(element);
965 	} ZEND_HASH_FOREACH_END();
966 	zend_hash_destroy(&watch->elements);
967 
968 	efree(watch);
969 }
970 
971 void phpdbg_clean_watch_element(phpdbg_watch_element *element) {
972 	phpdbg_unwatch_parent_ht(element);
973 
974 	if (element->watch) {
975 		HashTable *elements = &element->watch->elements;
976 		zend_hash_del(elements, element->str);
977 		if (zend_hash_num_elements(elements) == 0) {
978 			phpdbg_remove_watchpoint(element->watch);
979 		}
980 	}
981 }
982 
983 /* TODO: compile a name of all hit watchpoints (ids ??) */
984 zend_string *phpdbg_watchpoint_change_collision_name(phpdbg_watchpoint_t *watch) {
985 	phpdbg_watchpoint_t *parent;
986 	phpdbg_watch_element *element;
987 	zend_string *name = NULL;
988 	if (watch->coll) {
989 		ZEND_HASH_MAP_FOREACH_PTR(&watch->coll->parents, parent) {
990 			if (name) {
991 				zend_string_release(name);
992 			}
993 			name = phpdbg_watchpoint_change_collision_name(parent);
994 		} ZEND_HASH_FOREACH_END();
995 		return name;
996 	}
997 	ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
998 		if (element->flags & PHPDBG_WATCH_IMPLICIT) {
999 			if ((watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) && Z_TYPE(watch->backup.zv) > IS_STRING) {
1000 				phpdbg_update_watch_element_watch(element->child);
1001 			}
1002 			continue;
1003 		}
1004 		name = element->str;
1005 	} ZEND_HASH_FOREACH_END();
1006 
1007 	return name ? zend_string_copy(name) : NULL;
1008 }
1009 
1010 /* ### WATCHING FOR CHANGES ### */
1011 /* TODO: enforce order: first parents, then children, in order to avoid false positives */
1012 void phpdbg_check_watchpoint(phpdbg_watchpoint_t *watch) {
1013 	zend_string *name = NULL;
1014 	void *comparePtr;
1015 
1016 	if (watch->type == WATCH_ON_HASHTABLE) {
1017 		phpdbg_watch_element *element;
1018 		zend_string *str;
1019 		zend_long idx;
1020 		zval *zv;
1021 		ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
1022 			if (element->flags & PHPDBG_WATCH_RECURSIVE) {
1023 				phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) HT_WATCH_HT(watch));
1024 				phpdbg_watch_ht_info *hti = res ? res->ptr : NULL;
1025 
1026 				ZEND_HASH_REVERSE_FOREACH_KEY_VAL(HT_WATCH_HT(watch), idx, str, zv) {
1027 					if (!str) {
1028 						str = zend_long_to_str(idx); // TODO: hack, use proper int handling for name in parent
1029 					} else {
1030 						str = zend_string_copy(str);
1031 					}
1032 					if (hti && zend_hash_find(&hti->watches, str)) {
1033 						zend_string_release(str);
1034 						break;
1035 					}
1036 					ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
1037 						if (element->flags & PHPDBG_WATCH_RECURSIVE) {
1038 							phpdbg_add_recursive_watch_from_ht(element, idx, str, zv);
1039 						}
1040 					} ZEND_HASH_FOREACH_END();
1041 					phpdbg_notice("Element %.*s has been added to watchpoint", (int) ZSTR_LEN(str), ZSTR_VAL(str));
1042 					zend_string_release(str);
1043 					PHPDBG_G(watchpoint_hit) = 1;
1044 				} ZEND_HASH_FOREACH_END();
1045 
1046 				break;
1047 			}
1048 		} ZEND_HASH_FOREACH_END();
1049 	}
1050 	if (watch->type == WATCH_ON_HASHDATA) {
1051 		return;
1052 	}
1053 
1054 	switch (watch->type) {
1055 		case WATCH_ON_STR:
1056 			comparePtr = &ZSTR_LEN(watch->backup.str);
1057 			break;
1058 		case WATCH_ON_HASHTABLE:
1059 			comparePtr = (char *) &watch->backup.ht + HT_WATCH_OFFSET;
1060 			break;
1061 		default:
1062 			comparePtr = &watch->backup;
1063 	}
1064 	if (!phpdbg_check_watch_diff(watch->type, comparePtr, watch->addr.ptr)) {
1065 		return;
1066 	}
1067 	if (watch->type == WATCH_ON_REFCOUNTED && !(PHPDBG_G(flags) & PHPDBG_SHOW_REFCOUNTS)) {
1068 		phpdbg_watch_backup_data(watch);
1069 		return;
1070 	}
1071 	if (watch->type == WATCH_ON_BUCKET) {
1072 		if (watch->backup.bucket.key != watch->addr.bucket->key || (watch->backup.bucket.key != NULL && watch->backup.bucket.h != watch->addr.bucket->h)) {
1073 			phpdbg_watch_element *element = NULL;
1074 			zval *new;
1075 
1076 			ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
1077 				break;
1078 			} ZEND_HASH_FOREACH_END();
1079 
1080 			ZEND_ASSERT(element); /* elements must be non-empty */
1081 			new = zend_symtable_find(element->parent_container, element->name_in_parent);
1082 
1083 			if (!new) {
1084 				/* dequeuing will take care of appropriate notification about removal */
1085 				phpdbg_remove_watchpoint(watch);
1086 				return;
1087 			}
1088 
1089 			phpdbg_remove_watchpoint_btree(watch);
1090 			phpdbg_deactivate_watchpoint(watch);
1091 			watch->addr.zv = new;
1092 			phpdbg_store_watchpoint_btree(watch);
1093 			phpdbg_activate_watchpoint(watch);
1094 
1095 			if (!phpdbg_check_watch_diff(WATCH_ON_ZVAL, &watch->backup.bucket.val, watch->addr.ptr)) {
1096 				phpdbg_watch_backup_data(watch);
1097 				return;
1098 			}
1099 		} else if (Z_TYPE_P(watch->addr.zv) == IS_UNDEF) {
1100 			/* dequeuing will take care of appropriate notification about removal */
1101 			phpdbg_remove_watchpoint(watch);
1102 			return;
1103 		}
1104 	}
1105 
1106 	name = phpdbg_watchpoint_change_collision_name(watch);
1107 
1108 	if (name) {
1109 		phpdbg_print_watch_diff(watch->type, name, comparePtr, watch->addr.ptr);
1110 		zend_string_release(name);
1111 	}
1112 
1113 	if (watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) {
1114 		phpdbg_watch_element *element;
1115 		phpdbg_update_watch_ref(watch);
1116 		ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
1117 			if (element->flags & PHPDBG_WATCH_RECURSIVE) {
1118 				phpdbg_recurse_watch_element(element);
1119 			}
1120 		} ZEND_HASH_FOREACH_END();
1121 	}
1122 
1123 	phpdbg_watch_backup_data(watch);
1124 }
1125 
1126 void phpdbg_reenable_memory_watches(void) {
1127 	zend_ulong page;
1128 	phpdbg_btree_result *res;
1129 	phpdbg_watchpoint_t *watch;
1130 
1131 	ZEND_HASH_MAP_FOREACH_NUM_KEY(PHPDBG_G(watchlist_mem), page) {
1132 		/* Disable writing again if there are any watchers on that page */
1133 		res = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), page + phpdbg_pagesize - 1);
1134 		if (res) {
1135 			watch = res->ptr;
1136 			if ((char *) page < (char *) watch->addr.ptr + watch->size) {
1137 #ifdef HAVE_USERFAULTFD_WRITEFAULT
1138 				if (PHPDBG_G(watch_userfaultfd)) {
1139 					struct uffdio_writeprotect protect = {
1140 						.mode = UFFDIO_WRITEPROTECT_MODE_WP,
1141 						.range = {
1142 							.start = (__u64) page,
1143 							.len = phpdbg_pagesize
1144 						}
1145 					};
1146 					ioctl(PHPDBG_G(watch_userfaultfd), UFFDIO_WRITEPROTECT,  &protect);
1147 				} else
1148 #endif
1149 				{
1150 					mprotect((void *) page, phpdbg_pagesize, PROT_READ);
1151 				}
1152 			}
1153 		}
1154 	} ZEND_HASH_FOREACH_END();
1155 	zend_hash_clean(PHPDBG_G(watchlist_mem));
1156 }
1157 
1158 int phpdbg_print_changed_zvals(void) {
1159 	int ret;
1160 	zend_ulong page;
1161 	phpdbg_watchpoint_t *watch;
1162 	phpdbg_btree_result *res;
1163 	HashTable *mem_list = NULL;
1164 
1165 	if (zend_hash_num_elements(&PHPDBG_G(watch_elements)) == 0) {
1166 		return FAILURE;
1167 	}
1168 
1169 	if (zend_hash_num_elements(PHPDBG_G(watchlist_mem)) > 0) {
1170 		/* we must not add elements to the hashtable while iterating over it (resize => read into freed memory) */
1171 		mem_list = PHPDBG_G(watchlist_mem);
1172 		PHPDBG_G(watchlist_mem) = PHPDBG_G(watchlist_mem_backup);
1173 
1174 		ZEND_HASH_MAP_FOREACH_NUM_KEY(mem_list, page) {
1175 			phpdbg_btree_position pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), page, page + phpdbg_pagesize);
1176 
1177 			while ((res = phpdbg_btree_next(&pos))) {
1178 				watch = res->ptr;
1179 				phpdbg_check_watchpoint(watch);
1180 			}
1181 			if ((res = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), page - 1))) {
1182 				watch = res->ptr;
1183 				if ((char *) page < (char *) watch->addr.ptr + watch->size) {
1184 					phpdbg_check_watchpoint(watch);
1185 				}
1186 			}
1187 		} ZEND_HASH_FOREACH_END();
1188 	}
1189 
1190 	phpdbg_dequeue_elements_for_recreation();
1191 
1192 	phpdbg_reenable_memory_watches();
1193 
1194 	if (mem_list) {
1195 		PHPDBG_G(watchlist_mem) = mem_list;
1196 		phpdbg_reenable_memory_watches();
1197 	}
1198 
1199 	ret = PHPDBG_G(watchpoint_hit) ? SUCCESS : FAILURE;
1200 	PHPDBG_G(watchpoint_hit) = 0;
1201 
1202 	return ret;
1203 }
1204 
1205 void phpdbg_watch_efree(void *ptr) {
1206 	phpdbg_btree_result *result;
1207 
1208 	/* only do expensive checks if there are any watches at all */
1209 	if (zend_hash_num_elements(&PHPDBG_G(watch_elements))) {
1210 		if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) ptr))) {
1211 			phpdbg_watchpoint_t *watch = result->ptr;
1212 			if (watch->type != WATCH_ON_HASHDATA) {
1213 				phpdbg_remove_watchpoint(watch);
1214 			} else {
1215 				/* remove all linked watchpoints, they will be dissociated from their elements */
1216 				phpdbg_watch_element *element;
1217 				phpdbg_watch_ht_info *hti = (phpdbg_watch_ht_info *) watch;
1218 
1219 				ZEND_HASH_MAP_FOREACH_PTR(&hti->watches, element) {
1220 					zend_ulong num = zend_hash_num_elements(&hti->watches);
1221 					phpdbg_remove_watchpoint(element->watch);
1222 					if (num == 1) { /* prevent access into freed memory */
1223 						break;
1224 					}
1225 				} ZEND_HASH_FOREACH_END();
1226 			}
1227 		}
1228 
1229 		/* special case watchpoints as they aren't on ptr but on ptr + HT_WATCH_OFFSET */
1230 		if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), HT_WATCH_OFFSET + (zend_ulong) ptr))) {
1231 			phpdbg_watchpoint_t *watch = result->ptr;
1232 			if (watch->type == WATCH_ON_HASHTABLE) {
1233 				phpdbg_remove_watchpoint(watch);
1234 			}
1235 		}
1236 
1237 		zend_hash_index_del(&PHPDBG_G(watch_free), (zend_ulong) ptr);
1238 	}
1239 
1240 	if (PHPDBG_G(original_free_function)) {
1241 		PHPDBG_G(original_free_function)(ptr);
1242 	}
1243 }
1244 
1245 /* ### USER API ### */
1246 void phpdbg_list_watchpoints(void) {
1247 	phpdbg_watch_element *element;
1248 
1249 	ZEND_HASH_FOREACH_PTR(&PHPDBG_G(watch_elements), element) {
1250 		phpdbg_writeln("%.*s (%s, %s)", (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str), (element->flags & (PHPDBG_WATCH_ARRAY|PHPDBG_WATCH_OBJECT)) ? "array" : "variable", (element->flags & PHPDBG_WATCH_RECURSIVE) ? "recursive" : "simple");
1251 	} ZEND_HASH_FOREACH_END();
1252 }
1253 
1254 static int phpdbg_create_simple_watchpoint(zval *zv, phpdbg_watch_element *element) {
1255 	element->flags = PHPDBG_WATCH_SIMPLE;
1256 	phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1257 	return SUCCESS;
1258 }
1259 
1260 static int phpdbg_create_array_watchpoint(zval *zv, phpdbg_watch_element *element) {
1261 	phpdbg_watch_element *new;
1262 	zend_string *str;
1263 	zval *orig_zv = zv;
1264 
1265 	ZVAL_DEREF(zv);
1266 	if (Z_TYPE_P(zv) != IS_ARRAY && Z_TYPE_P(zv) != IS_OBJECT) {
1267 		return FAILURE;
1268 	}
1269 
1270 	new = ecalloc(1, sizeof(phpdbg_watch_element));
1271 
1272 	str = strpprintf(0, "%.*s[]", (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str));
1273 	zend_string_release(element->str);
1274 	element->str = str;
1275 	element->flags = PHPDBG_WATCH_IMPLICIT;
1276 	phpdbg_add_bucket_watch_element((Bucket *) orig_zv, element);
1277 	element->child = new;
1278 
1279 	new->flags = PHPDBG_WATCH_SIMPLE;
1280 	new->str = zend_string_copy(str);
1281 	new->parent = element;
1282 	phpdbg_add_ht_watch_element(zv, new);
1283 	return SUCCESS;
1284 }
1285 
1286 static int phpdbg_create_recursive_watchpoint(zval *zv, phpdbg_watch_element *element) {
1287 	element->flags = PHPDBG_WATCH_RECURSIVE | PHPDBG_WATCH_RECURSIVE_ROOT;
1288 	element->child = NULL;
1289 	phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1290 	return SUCCESS;
1291 }
1292 
1293 typedef struct { int (*callback)(zval *zv, phpdbg_watch_element *); zend_string *str; } phpdbg_watch_parse_struct;
1294 
1295 static int phpdbg_watchpoint_parse_wrapper(char *name, size_t namelen, char *key, size_t keylen, HashTable *parent, zval *zv, phpdbg_watch_parse_struct *info) {
1296 	int ret;
1297 	phpdbg_watch_element *element = ecalloc(1, sizeof(phpdbg_watch_element));
1298 	element->str = zend_string_init(name, namelen, 0);
1299 	element->name_in_parent = zend_string_init(key, keylen, 0);
1300 	element->parent_container = parent;
1301 	element->parent = PHPDBG_G(watch_tmp);
1302 	element->child = NULL;
1303 
1304 	ret = info->callback(zv, element);
1305 
1306 	efree(name);
1307 	efree(key);
1308 
1309 	if (ret != SUCCESS) {
1310 		phpdbg_remove_watch_element(element);
1311 	} else {
1312 		if (PHPDBG_G(watch_tmp)) {
1313 			PHPDBG_G(watch_tmp)->child = element;
1314 		}
1315 
1316 		if (element->child) {
1317 			element = element->child;
1318 		}
1319 
1320 		/* work around missing API for extending an array with a new element, and getting its index */
1321 		zend_hash_next_index_insert_ptr(&PHPDBG_G(watch_elements), element);
1322 		element->id = PHPDBG_G(watch_elements).nNextFreeElement - 1;
1323 
1324 		phpdbg_notice("Added%s watchpoint #%u for %.*s", (element->flags & PHPDBG_WATCH_RECURSIVE_ROOT) ? " recursive" : "", element->id, (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str));
1325 	}
1326 
1327 	PHPDBG_G(watch_tmp) = NULL;
1328 
1329 	return ret;
1330 }
1331 
1332 PHPDBG_API int phpdbg_watchpoint_parse_input(char *input, size_t len, HashTable *parent, size_t i, phpdbg_watch_parse_struct *info, bool silent) {
1333 	return phpdbg_parse_variable_with_arg(input, len, parent, i, (phpdbg_parse_var_with_arg_func) phpdbg_watchpoint_parse_wrapper, NULL, 0, info);
1334 }
1335 
1336 static int phpdbg_watchpoint_parse_step(char *name, size_t namelen, char *key, size_t keylen, HashTable *parent, zval *zv, phpdbg_watch_parse_struct *info) {
1337 	phpdbg_watch_element *element;
1338 
1339 	/* do not install watch elements for references */
1340 	if (PHPDBG_G(watch_tmp) && Z_ISREF_P(PHPDBG_G(watch_tmp)->watch->addr.zv) && Z_REFVAL_P(PHPDBG_G(watch_tmp)->watch->addr.zv) == zv) {
1341 		efree(name);
1342 		efree(key);
1343 		return SUCCESS;
1344 	}
1345 
1346 	element = ecalloc(1, sizeof(phpdbg_watch_element));
1347 	element->flags = PHPDBG_WATCH_IMPLICIT;
1348 	element->str = zend_string_copy(info->str);
1349 	element->name_in_parent = zend_string_init(key, keylen, 0);
1350 	element->parent_container = parent;
1351 	element->parent = PHPDBG_G(watch_tmp);
1352 	element = phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1353 
1354 	efree(name);
1355 	efree(key);
1356 
1357 	if (PHPDBG_G(watch_tmp)) {
1358 		PHPDBG_G(watch_tmp)->child = element;
1359 	}
1360 	PHPDBG_G(watch_tmp) = element;
1361 
1362 	return SUCCESS;
1363 }
1364 
1365 static int phpdbg_watchpoint_parse_symtables(char *input, size_t len, int (*callback)(zval *, phpdbg_watch_element *)) {
1366 	zend_class_entry *scope = zend_get_executed_scope();
1367 	phpdbg_watch_parse_struct info;
1368 	int ret;
1369 
1370 	if (scope && len >= 5 && !memcmp("$this", input, 5)) {
1371 		zend_hash_str_add(EG(current_execute_data)->symbol_table, ZEND_STRL("this"), &EG(current_execute_data)->This);
1372 	}
1373 
1374 	if (callback == phpdbg_create_array_watchpoint) {
1375 		info.str = strpprintf(0, "%.*s[]", (int) len, input);
1376 	} else {
1377 		info.str = zend_string_init(input, len, 0);
1378 	}
1379 	info.callback = callback;
1380 
1381 	if (phpdbg_is_auto_global(input, len) && phpdbg_watchpoint_parse_input(input, len, &EG(symbol_table), 0, &info, 1) != FAILURE) {
1382 		zend_string_release(info.str);
1383 		return SUCCESS;
1384 	}
1385 
1386 	ret = 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, &info);
1387 
1388 	zend_string_release(info.str);
1389 	return ret;
1390 }
1391 
1392 PHPDBG_WATCH(delete) /* {{{ */
1393 {
1394 	phpdbg_watch_element *element;
1395 	switch (param->type) {
1396 		case NUMERIC_PARAM:
1397 			if ((element = zend_hash_index_find_ptr(&PHPDBG_G(watch_elements), param->num))) {
1398 				phpdbg_remove_watch_element(element);
1399 				phpdbg_notice("Removed watchpoint %d", (int) param->num);
1400 			} else {
1401 				phpdbg_error("Nothing was deleted, no corresponding watchpoint found");
1402 			}
1403 			break;
1404 
1405 		phpdbg_default_switch_case();
1406 	}
1407 
1408 	return SUCCESS;
1409 } /* }}} */
1410 
1411 int phpdbg_create_var_watchpoint(char *input, size_t len) {
1412 	if (phpdbg_rebuild_symtable() == FAILURE) {
1413 		return FAILURE;
1414 	}
1415 
1416 	return phpdbg_watchpoint_parse_symtables(input, len, phpdbg_create_simple_watchpoint);
1417 }
1418 
1419 PHPDBG_WATCH(recursive) /* {{{ */
1420 {
1421 	if (phpdbg_rebuild_symtable() == FAILURE) {
1422 		return SUCCESS;
1423 	}
1424 
1425 	switch (param->type) {
1426 		case STR_PARAM:
1427 			phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_recursive_watchpoint);
1428 			break;
1429 
1430 		phpdbg_default_switch_case();
1431 	}
1432 
1433 	return SUCCESS;
1434 } /* }}} */
1435 
1436 PHPDBG_WATCH(array) /* {{{ */
1437 {
1438 	if (phpdbg_rebuild_symtable() == FAILURE) {
1439 		return SUCCESS;
1440 	}
1441 
1442 	switch (param->type) {
1443 		case STR_PARAM:
1444 			phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_array_watchpoint);
1445 			break;
1446 
1447 		phpdbg_default_switch_case();
1448 	}
1449 
1450 	return SUCCESS;
1451 } /* }}} */
1452 
1453 
1454 void phpdbg_setup_watchpoints(void) {
1455 #if defined(_SC_PAGE_SIZE)
1456 	phpdbg_pagesize = sysconf(_SC_PAGE_SIZE);
1457 #elif defined(_SC_PAGESIZE)
1458 	phpdbg_pagesize = sysconf(_SC_PAGESIZE);
1459 #elif defined(_SC_NUTC_OS_PAGESIZE)
1460 	phpdbg_pagesize = sysconf(_SC_NUTC_OS_PAGESIZE);
1461 #else
1462 	phpdbg_pagesize = 4096; /* common pagesize */
1463 #endif
1464 
1465 	phpdbg_btree_init(&PHPDBG_G(watchpoint_tree), sizeof(void *) * 8);
1466 	phpdbg_btree_init(&PHPDBG_G(watch_HashTables), sizeof(void *) * 8);
1467 	zend_hash_init(&PHPDBG_G(watch_elements), 8, NULL, NULL, 0);
1468 	zend_hash_init(&PHPDBG_G(watch_collisions), 8, NULL, NULL, 0);
1469 	zend_hash_init(&PHPDBG_G(watch_recreation), 8, NULL, NULL, 0);
1470 	zend_hash_init(&PHPDBG_G(watch_free), 8, NULL, NULL, 0);
1471 
1472 	/* put these on a separate page, to avoid conflicts with other memory */
1473 	PHPDBG_G(watchlist_mem) = malloc(phpdbg_pagesize > sizeof(HashTable) ? phpdbg_pagesize : sizeof(HashTable));
1474 	zend_hash_init(PHPDBG_G(watchlist_mem), phpdbg_pagesize / (sizeof(Bucket) + sizeof(uint32_t)), NULL, NULL, 1);
1475 	PHPDBG_G(watchlist_mem_backup) = malloc(phpdbg_pagesize > sizeof(HashTable) ? phpdbg_pagesize : sizeof(HashTable));
1476 	zend_hash_init(PHPDBG_G(watchlist_mem_backup), phpdbg_pagesize / (sizeof(Bucket) + sizeof(uint32_t)), NULL, NULL, 1);
1477 
1478 	PHPDBG_G(watch_tmp) = NULL;
1479 
1480 #ifdef HAVE_USERFAULTFD_WRITEFAULT
1481 	PHPDBG_G(watch_userfaultfd) = syscall(SYS_userfaultfd, O_CLOEXEC);
1482 	if (PHPDBG_G(watch_userfaultfd) < 0) {
1483 		PHPDBG_G(watch_userfaultfd) = 0;
1484 	} else {
1485 		struct uffdio_api userfaultfd_features = {0};
1486 		userfaultfd_features.api = UFFD_API;
1487 		userfaultfd_features.features = UFFD_FEATURE_PAGEFAULT_FLAG_WP;
1488 		ioctl(PHPDBG_G(watch_userfaultfd), UFFDIO_API, &userfaultfd_features);
1489 		if (userfaultfd_features.features & UFFD_FEATURE_PAGEFAULT_FLAG_WP) {
1490 			pthread_create(&PHPDBG_G(watch_userfault_thread), NULL, phpdbg_watchpoint_userfaultfd_thread, ZEND_MODULE_GLOBALS_BULK(phpdbg));
1491 		} else {
1492 			PHPDBG_G(watch_userfaultfd) = 0;
1493 		}
1494 	}
1495 #endif
1496 }
1497 
1498 void phpdbg_destroy_watchpoints(void) {
1499 	phpdbg_watch_element *element;
1500 
1501 	/* unconditionally free all remaining elements to avoid memory leaks */
1502 	ZEND_HASH_MAP_FOREACH_PTR(&PHPDBG_G(watch_recreation), element) {
1503 		phpdbg_automatic_dequeue_free(element);
1504 	} ZEND_HASH_FOREACH_END();
1505 
1506 	/* upon fatal errors etc. (i.e. CG(unclean_shutdown) == 1), some watchpoints may still be active. Ensure memory is not watched anymore for next run. Do not care about memory freeing here, shutdown is unclean and near anyway. */
1507     phpdbg_purge_watchpoint_tree();
1508 
1509 #ifdef HAVE_USERFAULTFD_WRITEFAULT
1510 	if (PHPDBG_G(watch_userfaultfd)) {
1511 		pthread_cancel(PHPDBG_G(watch_userfault_thread));
1512 		close(PHPDBG_G(watch_userfaultfd));
1513 	}
1514 #endif
1515 
1516 	zend_hash_destroy(&PHPDBG_G(watch_elements)); PHPDBG_G(watch_elements).nNumOfElements = 0; /* phpdbg_watch_efree() is checking against this arrays size */
1517 	zend_hash_destroy(&PHPDBG_G(watch_recreation));
1518 	zend_hash_destroy(&PHPDBG_G(watch_free));
1519 	zend_hash_destroy(&PHPDBG_G(watch_collisions));
1520 	zend_hash_destroy(PHPDBG_G(watchlist_mem));
1521 	free(PHPDBG_G(watchlist_mem));
1522 	zend_hash_destroy(PHPDBG_G(watchlist_mem_backup));
1523 	free(PHPDBG_G(watchlist_mem_backup));
1524 }
1525 
1526 void phpdbg_purge_watchpoint_tree(void) {
1527 	phpdbg_btree_position pos;
1528 	phpdbg_btree_result *res;
1529 
1530 	pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), 0, -1);
1531 	while ((res = phpdbg_btree_next(&pos))) {
1532 		phpdbg_deactivate_watchpoint(res->ptr);
1533 	}
1534 }
1535