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