xref: /PHP-8.2/sapi/phpdbg/phpdbg_watch.c (revision 2e26559f)
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 			if (element != old_element) {
520 				phpdbg_free_watch_element(element);
521 			}
522 			return old_element;
523 		}
524 	}
525 
526 	element->watch = watch;
527 	zend_hash_add_ptr(&watch->elements, element->str, element);
528 
529 	if (element->flags & PHPDBG_WATCH_RECURSIVE) {
530 		phpdbg_recurse_watch_element(element);
531 	}
532 
533 	return element;
534 }
535 
536 phpdbg_watch_element *phpdbg_add_bucket_watch_element(Bucket *bucket, phpdbg_watch_element *element) {
537 	phpdbg_watchpoint_t watch;
538 	phpdbg_set_bucket_watchpoint(bucket, &watch);
539 	element = phpdbg_add_watch_element(&watch, element);
540 	phpdbg_watch_parent_ht(element);
541 	return element;
542 }
543 
544 phpdbg_watch_element *phpdbg_add_ht_watch_element(zval *zv, phpdbg_watch_element *element) {
545 	phpdbg_watchpoint_t watch;
546 	HashTable *ht = HT_FROM_ZVP(zv);
547 
548 	if (!ht) {
549 		return NULL;
550 	}
551 
552 	element->flags |= Z_TYPE_P(zv) == IS_ARRAY ? PHPDBG_WATCH_ARRAY : PHPDBG_WATCH_OBJECT;
553 	phpdbg_set_ht_watchpoint(ht, &watch);
554 	return phpdbg_add_watch_element(&watch, element);
555 }
556 
557 bool phpdbg_is_recursively_watched(void *ptr, phpdbg_watch_element *element) {
558 	phpdbg_watch_element *next = element;
559 	do {
560 		element = next;
561 		if (element->watch->addr.ptr == ptr) {
562 			return 1;
563 		}
564 		next = element->parent;
565 	} while (!(element->flags & PHPDBG_WATCH_RECURSIVE_ROOT));
566 
567 	return 0;
568 }
569 
570 void phpdbg_add_recursive_watch_from_ht(phpdbg_watch_element *element, zend_long idx, zend_string *str, zval *zv) {
571 	phpdbg_watch_element *child;
572 	if (phpdbg_is_recursively_watched(zv, element)) {
573 		return;
574 	}
575 
576 	child = emalloc(sizeof(*child));
577 	child->flags = PHPDBG_WATCH_RECURSIVE;
578 	if (str) {
579 		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)));
580 	} else {
581 		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);
582 	}
583 	if (!str) {
584 		str = zend_long_to_str(idx); // TODO: hack, use proper int handling for name in parent
585 	} else { str = zend_string_copy(str); }
586 	child->name_in_parent = str;
587 	child->parent = element;
588 	child->child = NULL;
589 	child->parent_container = HT_WATCH_HT(element->watch);
590 	zend_hash_add_ptr(&element->child_container, child->str, child);
591 	phpdbg_add_bucket_watch_element((Bucket *) zv, child);
592 }
593 
594 void phpdbg_recurse_watch_element(phpdbg_watch_element *element) {
595 	phpdbg_watch_element *child;
596 	zval *zv;
597 
598 	if (element->watch->type == WATCH_ON_ZVAL || element->watch->type == WATCH_ON_BUCKET) {
599 		zv = element->watch->addr.zv;
600 		while (Z_TYPE_P(zv) == IS_INDIRECT) {
601 			zv = Z_INDIRECT_P(zv);
602 		}
603 		ZVAL_DEREF(zv);
604 
605 		if (element->child) {
606 			phpdbg_remove_watch_element_recursively(element->child);
607 		}
608 
609 		if ((Z_TYPE_P(zv) != IS_ARRAY && Z_TYPE_P(zv) != IS_OBJECT)
610 		    || phpdbg_is_recursively_watched(HT_WATCH_OFFSET + (char *) HT_FROM_ZVP(zv), element)) {
611 			if (element->child) {
612 				phpdbg_free_watch_element(element->child);
613 				element->child = NULL;
614 			}
615 			return;
616 		}
617 
618 		if (element->child) {
619 			child = element->child;
620 		} else {
621 			child = emalloc(sizeof(*child));
622 			child->flags = PHPDBG_WATCH_RECURSIVE;
623 			child->str = strpprintf(0, "%.*s[]", (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str));
624 			child->name_in_parent = NULL;
625 			child->parent = element;
626 			child->child = NULL;
627 			element->child = child;
628 		}
629 		zend_hash_init(&child->child_container, 8, NULL, NULL, 0);
630 		phpdbg_add_ht_watch_element(zv, child);
631 	} else if (zend_hash_num_elements(&element->child_container) == 0) {
632 		zend_string *str;
633 		zend_long idx;
634 
635 		ZEND_ASSERT(element->watch->type == WATCH_ON_HASHTABLE);
636 		ZEND_HASH_FOREACH_KEY_VAL(HT_WATCH_HT(element->watch), idx, str, zv) {
637 			phpdbg_add_recursive_watch_from_ht(element, idx, str, zv);
638 		} ZEND_HASH_FOREACH_END();
639 	}
640 }
641 
642 void phpdbg_watch_parent_ht(phpdbg_watch_element *element) {
643 	if (element->watch->type == WATCH_ON_BUCKET) {
644 		phpdbg_btree_result *res;
645 		HashPosition pos;
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, grrrrr, 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_internal_pointer_end_ex(hti->ht, &pos);
664 		hti->last = hti->ht->arData + pos;
665 		hti->last_str = hti->last->key;
666 		hti->last_idx = hti->last->h;
667 
668 		zend_hash_add_ptr(&hti->watches, element->name_in_parent, element);
669 	}
670 }
671 
672 void phpdbg_unwatch_parent_ht(phpdbg_watch_element *element) {
673 	if (element->watch && element->watch->type == WATCH_ON_BUCKET) {
674 		phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) element->parent_container);
675 		ZEND_ASSERT(element->parent_container);
676 		if (res) {
677 			phpdbg_watch_ht_info *hti = res->ptr;
678 
679 			if (zend_hash_num_elements(&hti->watches) == 1) {
680 				zend_hash_destroy(&hti->watches);
681 				phpdbg_btree_delete(&PHPDBG_G(watch_HashTables), (zend_ulong) hti->ht);
682 				phpdbg_remove_watchpoint_btree(&hti->hash_watch);
683 				phpdbg_deactivate_watchpoint(&hti->hash_watch);
684 				efree(hti);
685 			} else {
686 				zend_hash_del(&hti->watches, element->name_in_parent);
687 			}
688 		}
689 	}
690 }
691 
692 /* ### DE/QUEUE WATCH ELEMENTS ### to be used by watch element manager only */
693 /* 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 */
694 
695 void phpdbg_dissociate_watch_element(phpdbg_watch_element *element, phpdbg_watch_element *until);
696 void phpdbg_free_watch_element_tree(phpdbg_watch_element *element);
697 
698 void phpdbg_queue_element_for_recreation(phpdbg_watch_element *element) {
699 	/* store lowermost element */
700 	phpdbg_watch_element *prev;
701 
702 	if ((prev = zend_hash_find_ptr(&PHPDBG_G(watch_recreation), element->str))) {
703 		phpdbg_watch_element *child = prev;
704 		do {
705 			if (child == element) {
706 				return;
707 			}
708 			child = child->child;
709 		} while (child);
710 	}
711 	zend_hash_update_ptr(&PHPDBG_G(watch_recreation), element->str, element);
712 
713 	/* dissociate from watchpoint to avoid dangling memory watches */
714 	phpdbg_dissociate_watch_element(element, prev);
715 
716 	if (!element->parent) {
717 		/* 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) */
718 		zend_hash_index_add_empty_element(&PHPDBG_G(watch_free), (zend_ulong) element->parent_container);
719 	}
720 }
721 
722 bool phpdbg_try_re_adding_watch_element(zval *parent, phpdbg_watch_element *element) {
723 	zval *zv;
724 	HashTable *ht = HT_FROM_ZVP(parent);
725 
726 	if (!ht) {
727 		return 0;
728 	} else if (element->flags & (PHPDBG_WATCH_ARRAY | PHPDBG_WATCH_OBJECT)) {
729 		char *htPtr = ((char *) ht) + HT_WATCH_OFFSET;
730 		char *oldPtr = ((char *) &element->backup.ht) + HT_WATCH_OFFSET;
731 		if (phpdbg_check_watch_diff(WATCH_ON_HASHTABLE, oldPtr, htPtr)) {
732 			phpdbg_print_watch_diff(WATCH_ON_HASHTABLE, element->str, oldPtr, htPtr);
733 		}
734 
735 		phpdbg_add_ht_watch_element(parent, element);
736 	} else if ((zv = zend_symtable_find(ht, element->name_in_parent))) {
737 		if (element->flags & PHPDBG_WATCH_IMPLICIT) {
738 			zval *next = zv;
739 
740 			while (Z_TYPE_P(next) == IS_INDIRECT) {
741 				next = Z_INDIRECT_P(next);
742 			}
743 			if (Z_ISREF_P(next)) {
744 				next = Z_REFVAL_P(next);
745 			}
746 
747 			if (!phpdbg_try_re_adding_watch_element(next, element->child)) {
748 				return 0;
749 			}
750 		} else if (phpdbg_check_watch_diff(WATCH_ON_ZVAL, &element->backup.zv, zv)) {
751 			phpdbg_print_watch_diff(WATCH_ON_ZVAL, element->str, &element->backup.zv, zv);
752 		}
753 
754 		element->parent_container = ht;
755 		phpdbg_add_bucket_watch_element((Bucket *) zv, element);
756 		phpdbg_watch_parent_ht(element);
757 	} else {
758 		return 0;
759 	}
760 
761 	return 1;
762 }
763 
764 void phpdbg_automatic_dequeue_free(phpdbg_watch_element *element) {
765 	phpdbg_watch_element *child = element;
766 	while (child->child && !(child->flags & PHPDBG_WATCH_RECURSIVE_ROOT)) {
767 		child = child->child;
768 	}
769 	PHPDBG_G(watchpoint_hit) = 1;
770 	if (zend_hash_index_del(&PHPDBG_G(watch_elements), child->id) == SUCCESS) {
771 		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" : "");
772 	}
773 	phpdbg_free_watch_element_tree(element);
774 }
775 
776 void phpdbg_dequeue_elements_for_recreation(void) {
777 	phpdbg_watch_element *element;
778 
779 	ZEND_HASH_MAP_FOREACH_PTR(&PHPDBG_G(watch_recreation), element) {
780 		ZEND_ASSERT(element->flags & (PHPDBG_WATCH_IMPLICIT | PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_SIMPLE));
781 		if (element->parent || zend_hash_index_find(&PHPDBG_G(watch_free), (zend_ulong) element->parent_container)) {
782 			zval _zv, *zv = &_zv;
783 			if (element->parent) {
784 				ZEND_ASSERT(element->parent->watch->type == WATCH_ON_ZVAL || element->parent->watch->type == WATCH_ON_BUCKET);
785 				zv = element->parent->watch->addr.zv;
786 				while (Z_TYPE_P(zv) == IS_INDIRECT) {
787 					zv = Z_INDIRECT_P(zv);
788 				}
789 				ZVAL_DEREF(zv);
790 			} else {
791 				ZVAL_ARR(zv, element->parent_container);
792 			}
793 			if (!phpdbg_try_re_adding_watch_element(zv, element)) {
794 				phpdbg_automatic_dequeue_free(element);
795 			}
796 		} else {
797 			phpdbg_automatic_dequeue_free(element);
798 		}
799 	} ZEND_HASH_FOREACH_END();
800 
801 	zend_hash_clean(&PHPDBG_G(watch_recreation));
802 	zend_hash_clean(&PHPDBG_G(watch_free));
803 }
804 
805 /* ### WATCH ELEMENT DELETION ### only use phpdbg_remove_watch_element from the exterior */
806 void phpdbg_clean_watch_element(phpdbg_watch_element *element);
807 
808 void phpdbg_free_watch_element(phpdbg_watch_element *element) {
809 	zend_string_release(element->str);
810 	if (element->name_in_parent) {
811 		zend_string_release(element->name_in_parent);
812 	}
813 	efree(element);
814 }
815 
816 /* note: does *not* free the passed element, only clean */
817 void phpdbg_remove_watch_element_recursively(phpdbg_watch_element *element) {
818 	if (element->child) {
819 		phpdbg_remove_watch_element_recursively(element->child);
820 		phpdbg_free_watch_element(element->child);
821 		element->child = NULL;
822 	} else if (element->flags & (PHPDBG_WATCH_ARRAY | PHPDBG_WATCH_OBJECT)) {
823 		phpdbg_watch_element *child;
824 		ZEND_HASH_MAP_FOREACH_PTR(&element->child_container, child) {
825 			phpdbg_remove_watch_element_recursively(child);
826 			phpdbg_free_watch_element(child);
827 		} ZEND_HASH_FOREACH_END();
828 		zend_hash_destroy(&element->child_container);
829 	}
830 
831 	phpdbg_clean_watch_element(element);
832 }
833 
834 /* remove single watch (i.e. manual unset) or implicit removed */
835 void phpdbg_remove_watch_element(phpdbg_watch_element *element) {
836 	phpdbg_watch_element *parent = element->parent, *child = element->child;
837 	while (parent) {
838 		phpdbg_watch_element *cur = parent;
839 		parent = parent->parent;
840 		phpdbg_clean_watch_element(cur);
841 		phpdbg_free_watch_element(cur);
842 	}
843 	while (child) {
844 		phpdbg_watch_element *cur = child;
845 		child = child->child;
846 		if (cur->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
847 			phpdbg_remove_watch_element_recursively(cur);
848 			child = NULL;
849 		} else {
850 			phpdbg_clean_watch_element(cur);
851 		}
852 		phpdbg_free_watch_element(cur);
853 	}
854 	if (element->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
855 		phpdbg_remove_watch_element_recursively(element);
856 	} else {
857 		phpdbg_clean_watch_element(element);
858 	}
859 	zend_hash_index_del(&PHPDBG_G(watch_elements), element->id);
860 	phpdbg_free_watch_element(element);
861 }
862 
863 void phpdbg_backup_watch_element(phpdbg_watch_element *element) {
864 	memcpy(&element->backup, &element->watch->backup, /* element->watch->size */ sizeof(element->backup));
865 }
866 
867 /* until argument to prevent double remove of children elements */
868 void phpdbg_dissociate_watch_element(phpdbg_watch_element *element, phpdbg_watch_element *until) {
869 	phpdbg_watch_element *child = element;
870 	ZEND_ASSERT((element->flags & (PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_RECURSIVE)) != PHPDBG_WATCH_RECURSIVE);
871 
872 	if (element->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
873 		phpdbg_backup_watch_element(element);
874 		phpdbg_remove_watch_element_recursively(element);
875 		return;
876 	}
877 
878 	while (child->child != until) {
879 		child = child->child;
880 		if (child->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
881 			phpdbg_backup_watch_element(child);
882 			phpdbg_remove_watch_element_recursively(child);
883 			child->child = NULL;
884 			break;
885 		}
886 		if (child->child == NULL || (child->flags & PHPDBG_WATCH_RECURSIVE_ROOT)) {
887 			phpdbg_backup_watch_element(child);
888 		}
889 		phpdbg_clean_watch_element(child);
890 	}
891 	/* element needs to be removed last! */
892 	if (element->child == NULL) {
893 		phpdbg_backup_watch_element(element);
894 	}
895 	phpdbg_clean_watch_element(element);
896 }
897 
898 /* 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) */
899 void phpdbg_free_watch_element_tree(phpdbg_watch_element *element) {
900 	phpdbg_watch_element *parent = element->parent, *child = element->child;
901 	while (parent) {
902 		phpdbg_watch_element *cur = parent;
903 		parent = parent->parent;
904 		phpdbg_clean_watch_element(cur);
905 		phpdbg_free_watch_element(cur);
906 	}
907 	while (child) {
908 		phpdbg_watch_element *cur = child;
909 		child = child->child;
910 		phpdbg_free_watch_element(cur);
911 	}
912 	phpdbg_free_watch_element(element);
913 }
914 
915 void phpdbg_update_watch_element_watch(phpdbg_watch_element *element) {
916 	if (element->flags & PHPDBG_WATCH_IMPLICIT) {
917 		phpdbg_watch_element *child = element->child;
918 		while (child->flags & PHPDBG_WATCH_IMPLICIT) {
919 			child = child->child;
920 		}
921 
922 		ZEND_ASSERT(element->watch->type == WATCH_ON_ZVAL || element->watch->type == WATCH_ON_BUCKET);
923 		phpdbg_queue_element_for_recreation(element);
924 	} else if (element->flags & (PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_SIMPLE)) {
925 		phpdbg_queue_element_for_recreation(element);
926 	} else if (element->flags & PHPDBG_WATCH_RECURSIVE) {
927 		phpdbg_remove_watch_element_recursively(element);
928 		if (element->parent->flags & (PHPDBG_WATCH_OBJECT | PHPDBG_WATCH_ARRAY)) {
929 			zend_hash_del(&element->parent->child_container, element->str);
930 		} else {
931 			element->parent->child = NULL;
932 		}
933 		phpdbg_free_watch_element(element);
934 	}
935 }
936 
937 void phpdbg_update_watch_collision_elements(phpdbg_watchpoint_t *watch) {
938 	phpdbg_watchpoint_t *parent;
939 	phpdbg_watch_element *element;
940 
941 	ZEND_HASH_MAP_FOREACH_PTR(&watch->coll->parents, parent) {
942 		if (parent->coll) {
943 			phpdbg_update_watch_collision_elements(parent);
944 		} else {
945 			ZEND_HASH_MAP_FOREACH_PTR(&parent->elements, element) {
946 				phpdbg_update_watch_element_watch(element);
947 			} ZEND_HASH_FOREACH_END();
948 		}
949 	} ZEND_HASH_FOREACH_END();
950 }
951 
952 void phpdbg_remove_watchpoint(phpdbg_watchpoint_t *watch) {
953 	phpdbg_watch_element *element;
954 
955 	phpdbg_remove_watchpoint_btree(watch);
956 	phpdbg_deactivate_watchpoint(watch);
957 	phpdbg_delete_watch_collision(watch);
958 
959 	if (watch->coll) {
960 		phpdbg_update_watch_collision_elements(watch);
961 		return;
962 	}
963 
964 	watch->elements.nNumOfElements++; /* dirty hack to avoid double free */
965 	ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
966 		phpdbg_update_watch_element_watch(element);
967 	} ZEND_HASH_FOREACH_END();
968 	zend_hash_destroy(&watch->elements);
969 
970 	efree(watch);
971 }
972 
973 void phpdbg_clean_watch_element(phpdbg_watch_element *element) {
974 	phpdbg_unwatch_parent_ht(element);
975 
976 	if (element->watch) {
977 		HashTable *elements = &element->watch->elements;
978 		zend_hash_del(elements, element->str);
979 		if (zend_hash_num_elements(elements) == 0) {
980 			phpdbg_remove_watchpoint(element->watch);
981 		}
982 	}
983 }
984 
985 /* TODO: compile a name of all hit watchpoints (ids ??) */
986 zend_string *phpdbg_watchpoint_change_collision_name(phpdbg_watchpoint_t *watch) {
987 	phpdbg_watchpoint_t *parent;
988 	phpdbg_watch_element *element;
989 	zend_string *name = NULL;
990 	if (watch->coll) {
991 		ZEND_HASH_MAP_FOREACH_PTR(&watch->coll->parents, parent) {
992 			if (name) {
993 				zend_string_release(name);
994 			}
995 			name = phpdbg_watchpoint_change_collision_name(parent);
996 		} ZEND_HASH_FOREACH_END();
997 		return name;
998 	}
999 	ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
1000 		if (element->flags & PHPDBG_WATCH_IMPLICIT) {
1001 			if ((watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) && Z_TYPE(watch->backup.zv) > IS_STRING) {
1002 				phpdbg_update_watch_element_watch(element->child);
1003 			}
1004 			continue;
1005 		}
1006 		name = element->str;
1007 	} ZEND_HASH_FOREACH_END();
1008 
1009 	return name ? zend_string_copy(name) : NULL;
1010 }
1011 
1012 /* ### WATCHING FOR CHANGES ### */
1013 /* TODO: enforce order: first parents, then children, in order to avoid false positives */
1014 void phpdbg_check_watchpoint(phpdbg_watchpoint_t *watch) {
1015 	zend_string *name = NULL;
1016 	void *comparePtr;
1017 
1018 	if (watch->type == WATCH_ON_HASHTABLE) {
1019 		phpdbg_watch_element *element;
1020 		zend_string *str;
1021 		zend_long idx;
1022 		zval *zv;
1023 		ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
1024 			if (element->flags & PHPDBG_WATCH_RECURSIVE) {
1025 				phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) HT_WATCH_HT(watch));
1026 				phpdbg_watch_ht_info *hti = res ? res->ptr : NULL;
1027 
1028 				ZEND_HASH_REVERSE_FOREACH_KEY_VAL(HT_WATCH_HT(watch), idx, str, zv) {
1029 					if (!str) {
1030 						str = zend_long_to_str(idx); // TODO: hack, use proper int handling for name in parent
1031 					} else {
1032 						str = zend_string_copy(str);
1033 					}
1034 					if (hti && zend_hash_find(&hti->watches, str)) {
1035 						zend_string_release(str);
1036 						break;
1037 					}
1038 					ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
1039 						if (element->flags & PHPDBG_WATCH_RECURSIVE) {
1040 							phpdbg_add_recursive_watch_from_ht(element, idx, str, zv);
1041 						}
1042 					} ZEND_HASH_FOREACH_END();
1043 					phpdbg_notice("Element %.*s has been added to watchpoint", (int) ZSTR_LEN(str), ZSTR_VAL(str));
1044 					zend_string_release(str);
1045 					PHPDBG_G(watchpoint_hit) = 1;
1046 				} ZEND_HASH_FOREACH_END();
1047 
1048 				break;
1049 			}
1050 		} ZEND_HASH_FOREACH_END();
1051 	}
1052 	if (watch->type == WATCH_ON_HASHDATA) {
1053 		return;
1054 	}
1055 
1056 	switch (watch->type) {
1057 		case WATCH_ON_STR:
1058 			comparePtr = &ZSTR_LEN(watch->backup.str);
1059 			break;
1060 		case WATCH_ON_HASHTABLE:
1061 			comparePtr = (char *) &watch->backup.ht + HT_WATCH_OFFSET;
1062 			break;
1063 		default:
1064 			comparePtr = &watch->backup;
1065 	}
1066 	if (!phpdbg_check_watch_diff(watch->type, comparePtr, watch->addr.ptr)) {
1067 		return;
1068 	}
1069 	if (watch->type == WATCH_ON_REFCOUNTED && !(PHPDBG_G(flags) & PHPDBG_SHOW_REFCOUNTS)) {
1070 		phpdbg_watch_backup_data(watch);
1071 		return;
1072 	}
1073 	if (watch->type == WATCH_ON_BUCKET) {
1074 		if (watch->backup.bucket.key != watch->addr.bucket->key || (watch->backup.bucket.key != NULL && watch->backup.bucket.h != watch->addr.bucket->h)) {
1075 			phpdbg_watch_element *element = NULL;
1076 			zval *new;
1077 
1078 			ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
1079 				break;
1080 			} ZEND_HASH_FOREACH_END();
1081 
1082 			ZEND_ASSERT(element); /* elements must be non-empty */
1083 			new = zend_symtable_find(element->parent_container, element->name_in_parent);
1084 
1085 			if (!new) {
1086 				/* dequeuing will take care of appropriate notification about removal */
1087 				phpdbg_remove_watchpoint(watch);
1088 				return;
1089 			}
1090 
1091 			phpdbg_remove_watchpoint_btree(watch);
1092 			phpdbg_deactivate_watchpoint(watch);
1093 			watch->addr.zv = new;
1094 			phpdbg_store_watchpoint_btree(watch);
1095 			phpdbg_activate_watchpoint(watch);
1096 
1097 			if (!phpdbg_check_watch_diff(WATCH_ON_ZVAL, &watch->backup.bucket.val, watch->addr.ptr)) {
1098 				phpdbg_watch_backup_data(watch);
1099 				return;
1100 			}
1101 		} else if (Z_TYPE_P(watch->addr.zv) == IS_UNDEF) {
1102 			/* dequeuing will take care of appropriate notification about removal */
1103 			phpdbg_remove_watchpoint(watch);
1104 			return;
1105 		}
1106 	}
1107 
1108 	name = phpdbg_watchpoint_change_collision_name(watch);
1109 
1110 	if (name) {
1111 		phpdbg_print_watch_diff(watch->type, name, comparePtr, watch->addr.ptr);
1112 		zend_string_release(name);
1113 	}
1114 
1115 	if (watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) {
1116 		phpdbg_watch_element *element;
1117 		phpdbg_update_watch_ref(watch);
1118 		ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
1119 			if (element->flags & PHPDBG_WATCH_RECURSIVE) {
1120 				phpdbg_recurse_watch_element(element);
1121 			}
1122 		} ZEND_HASH_FOREACH_END();
1123 	}
1124 
1125 	phpdbg_watch_backup_data(watch);
1126 }
1127 
1128 void phpdbg_reenable_memory_watches(void) {
1129 	zend_ulong page;
1130 	phpdbg_btree_result *res;
1131 	phpdbg_watchpoint_t *watch;
1132 
1133 	ZEND_HASH_MAP_FOREACH_NUM_KEY(PHPDBG_G(watchlist_mem), page) {
1134 		/* Disable writing again if there are any watchers on that page */
1135 		res = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), page + phpdbg_pagesize - 1);
1136 		if (res) {
1137 			watch = res->ptr;
1138 			if ((char *) page < (char *) watch->addr.ptr + watch->size) {
1139 #ifdef HAVE_USERFAULTFD_WRITEFAULT
1140 				if (PHPDBG_G(watch_userfaultfd)) {
1141 					struct uffdio_writeprotect protect = {
1142 						.mode = UFFDIO_WRITEPROTECT_MODE_WP,
1143 						.range = {
1144 							.start = (__u64) page,
1145 							.len = phpdbg_pagesize
1146 						}
1147 					};
1148 					ioctl(PHPDBG_G(watch_userfaultfd), UFFDIO_WRITEPROTECT,  &protect);
1149 				} else
1150 #endif
1151 				{
1152 					mprotect((void *) page, phpdbg_pagesize, PROT_READ);
1153 				}
1154 			}
1155 		}
1156 	} ZEND_HASH_FOREACH_END();
1157 	zend_hash_clean(PHPDBG_G(watchlist_mem));
1158 }
1159 
1160 int phpdbg_print_changed_zvals(void) {
1161 	int ret;
1162 	zend_ulong page;
1163 	phpdbg_watchpoint_t *watch;
1164 	phpdbg_btree_result *res;
1165 	HashTable *mem_list = NULL;
1166 
1167 	if (zend_hash_num_elements(&PHPDBG_G(watch_elements)) == 0) {
1168 		return FAILURE;
1169 	}
1170 
1171 	if (zend_hash_num_elements(PHPDBG_G(watchlist_mem)) > 0) {
1172 		/* we must not add elements to the hashtable while iterating over it (resize => read into freed memory) */
1173 		mem_list = PHPDBG_G(watchlist_mem);
1174 		PHPDBG_G(watchlist_mem) = PHPDBG_G(watchlist_mem_backup);
1175 
1176 		ZEND_HASH_MAP_FOREACH_NUM_KEY(mem_list, page) {
1177 			phpdbg_btree_position pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), page, page + phpdbg_pagesize);
1178 
1179 			while ((res = phpdbg_btree_next(&pos))) {
1180 				watch = res->ptr;
1181 				phpdbg_check_watchpoint(watch);
1182 			}
1183 			if ((res = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), page - 1))) {
1184 				watch = res->ptr;
1185 				if ((char *) page < (char *) watch->addr.ptr + watch->size) {
1186 					phpdbg_check_watchpoint(watch);
1187 				}
1188 			}
1189 		} ZEND_HASH_FOREACH_END();
1190 	}
1191 
1192 	phpdbg_dequeue_elements_for_recreation();
1193 
1194 	phpdbg_reenable_memory_watches();
1195 
1196 	if (mem_list) {
1197 		PHPDBG_G(watchlist_mem) = mem_list;
1198 		phpdbg_reenable_memory_watches();
1199 	}
1200 
1201 	ret = PHPDBG_G(watchpoint_hit) ? SUCCESS : FAILURE;
1202 	PHPDBG_G(watchpoint_hit) = 0;
1203 
1204 	return ret;
1205 }
1206 
1207 void phpdbg_watch_efree(void *ptr) {
1208 	phpdbg_btree_result *result;
1209 
1210 	/* only do expensive checks if there are any watches at all */
1211 	if (zend_hash_num_elements(&PHPDBG_G(watch_elements))) {
1212 		if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) ptr))) {
1213 			phpdbg_watchpoint_t *watch = result->ptr;
1214 			if (watch->type != WATCH_ON_HASHDATA) {
1215 				phpdbg_remove_watchpoint(watch);
1216 			} else {
1217 				/* remove all linked watchpoints, they will be dissociated from their elements */
1218 				phpdbg_watch_element *element;
1219 				phpdbg_watch_ht_info *hti = (phpdbg_watch_ht_info *) watch;
1220 
1221 				ZEND_HASH_MAP_FOREACH_PTR(&hti->watches, element) {
1222 					zend_ulong num = zend_hash_num_elements(&hti->watches);
1223 					phpdbg_remove_watchpoint(element->watch);
1224 					if (num == 1) { /* prevent access into freed memory */
1225 						break;
1226 					}
1227 				} ZEND_HASH_FOREACH_END();
1228 			}
1229 		}
1230 
1231 		/* special case watchpoints as they aren't on ptr but on ptr + HT_WATCH_OFFSET */
1232 		if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), HT_WATCH_OFFSET + (zend_ulong) ptr))) {
1233 			phpdbg_watchpoint_t *watch = result->ptr;
1234 			if (watch->type == WATCH_ON_HASHTABLE) {
1235 				phpdbg_remove_watchpoint(watch);
1236 			}
1237 		}
1238 
1239 		zend_hash_index_del(&PHPDBG_G(watch_free), (zend_ulong) ptr);
1240 	}
1241 
1242 	if (PHPDBG_G(original_free_function)) {
1243 		PHPDBG_G(original_free_function)(ptr);
1244 	}
1245 }
1246 
1247 /* ### USER API ### */
1248 void phpdbg_list_watchpoints(void) {
1249 	phpdbg_watch_element *element;
1250 
1251 	ZEND_HASH_FOREACH_PTR(&PHPDBG_G(watch_elements), element) {
1252 		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");
1253 	} ZEND_HASH_FOREACH_END();
1254 }
1255 
1256 static int phpdbg_create_simple_watchpoint(zval *zv, phpdbg_watch_element *element) {
1257 	element->flags = PHPDBG_WATCH_SIMPLE;
1258 	phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1259 	return SUCCESS;
1260 }
1261 
1262 static int phpdbg_create_array_watchpoint(zval *zv, phpdbg_watch_element *element) {
1263 	phpdbg_watch_element *new;
1264 	zend_string *str;
1265 	zval *orig_zv = zv;
1266 
1267 	ZVAL_DEREF(zv);
1268 	if (Z_TYPE_P(zv) != IS_ARRAY && Z_TYPE_P(zv) != IS_OBJECT) {
1269 		return FAILURE;
1270 	}
1271 
1272 	new = ecalloc(1, sizeof(phpdbg_watch_element));
1273 
1274 	str = strpprintf(0, "%.*s[]", (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str));
1275 	zend_string_release(element->str);
1276 	element->str = str;
1277 	element->flags = PHPDBG_WATCH_IMPLICIT;
1278 	phpdbg_add_bucket_watch_element((Bucket *) orig_zv, element);
1279 	element->child = new;
1280 
1281 	new->flags = PHPDBG_WATCH_SIMPLE;
1282 	new->str = zend_string_copy(str);
1283 	new->parent = element;
1284 	phpdbg_add_ht_watch_element(zv, new);
1285 	return SUCCESS;
1286 }
1287 
1288 static int phpdbg_create_recursive_watchpoint(zval *zv, phpdbg_watch_element *element) {
1289 	element->flags = PHPDBG_WATCH_RECURSIVE | PHPDBG_WATCH_RECURSIVE_ROOT;
1290 	element->child = NULL;
1291 	phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1292 	return SUCCESS;
1293 }
1294 
1295 typedef struct { int (*callback)(zval *zv, phpdbg_watch_element *); zend_string *str; } phpdbg_watch_parse_struct;
1296 
1297 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) {
1298 	int ret;
1299 	phpdbg_watch_element *element = ecalloc(1, sizeof(phpdbg_watch_element));
1300 	element->str = zend_string_init(name, namelen, 0);
1301 	element->name_in_parent = zend_string_init(key, keylen, 0);
1302 	element->parent_container = parent;
1303 	element->parent = PHPDBG_G(watch_tmp);
1304 	element->child = NULL;
1305 
1306 	ret = info->callback(zv, element);
1307 
1308 	efree(name);
1309 	efree(key);
1310 
1311 	if (ret != SUCCESS) {
1312 		phpdbg_remove_watch_element(element);
1313 	} else {
1314 		if (PHPDBG_G(watch_tmp)) {
1315 			PHPDBG_G(watch_tmp)->child = element;
1316 		}
1317 
1318 		if (element->child) {
1319 			element = element->child;
1320 		}
1321 
1322 		/* work around missing API for extending an array with a new element, and getting its index */
1323 		zend_hash_next_index_insert_ptr(&PHPDBG_G(watch_elements), element);
1324 		element->id = PHPDBG_G(watch_elements).nNextFreeElement - 1;
1325 
1326 		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));
1327 	}
1328 
1329 	PHPDBG_G(watch_tmp) = NULL;
1330 
1331 	return ret;
1332 }
1333 
1334 PHPDBG_API int phpdbg_watchpoint_parse_input(char *input, size_t len, HashTable *parent, size_t i, phpdbg_watch_parse_struct *info, bool silent) {
1335 	return phpdbg_parse_variable_with_arg(input, len, parent, i, (phpdbg_parse_var_with_arg_func) phpdbg_watchpoint_parse_wrapper, NULL, 0, info);
1336 }
1337 
1338 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) {
1339 	phpdbg_watch_element *element;
1340 
1341 	/* do not install watch elements for references */
1342 	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) {
1343 		efree(name);
1344 		efree(key);
1345 		return SUCCESS;
1346 	}
1347 
1348 	element = ecalloc(1, sizeof(phpdbg_watch_element));
1349 	element->flags = PHPDBG_WATCH_IMPLICIT;
1350 	element->str = zend_string_copy(info->str);
1351 	element->name_in_parent = zend_string_init(key, keylen, 0);
1352 	element->parent_container = parent;
1353 	element->parent = PHPDBG_G(watch_tmp);
1354 	element = phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1355 
1356 	efree(name);
1357 	efree(key);
1358 
1359 	if (PHPDBG_G(watch_tmp)) {
1360 		PHPDBG_G(watch_tmp)->child = element;
1361 	}
1362 	PHPDBG_G(watch_tmp) = element;
1363 
1364 	return SUCCESS;
1365 }
1366 
1367 static int phpdbg_watchpoint_parse_symtables(char *input, size_t len, int (*callback)(zval *, phpdbg_watch_element *)) {
1368 	zend_class_entry *scope = zend_get_executed_scope();
1369 	phpdbg_watch_parse_struct info;
1370 	int ret;
1371 
1372 	if (scope && len >= 5 && !memcmp("$this", input, 5)) {
1373 		zend_hash_str_add(EG(current_execute_data)->symbol_table, ZEND_STRL("this"), &EG(current_execute_data)->This);
1374 	}
1375 
1376 	if (callback == phpdbg_create_array_watchpoint) {
1377 		info.str = strpprintf(0, "%.*s[]", (int) len, input);
1378 	} else {
1379 		info.str = zend_string_init(input, len, 0);
1380 	}
1381 	info.callback = callback;
1382 
1383 	if (phpdbg_is_auto_global(input, len) && phpdbg_watchpoint_parse_input(input, len, &EG(symbol_table), 0, &info, 1) != FAILURE) {
1384 		zend_string_release(info.str);
1385 		return SUCCESS;
1386 	}
1387 
1388 	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);
1389 
1390 	zend_string_release(info.str);
1391 	return ret;
1392 }
1393 
1394 PHPDBG_WATCH(delete) /* {{{ */
1395 {
1396 	phpdbg_watch_element *element;
1397 	switch (param->type) {
1398 		case NUMERIC_PARAM:
1399 			if ((element = zend_hash_index_find_ptr(&PHPDBG_G(watch_elements), param->num))) {
1400 				phpdbg_remove_watch_element(element);
1401 				phpdbg_notice("Removed watchpoint %d", (int) param->num);
1402 			} else {
1403 				phpdbg_error("Nothing was deleted, no corresponding watchpoint found");
1404 			}
1405 			break;
1406 
1407 		phpdbg_default_switch_case();
1408 	}
1409 
1410 	return SUCCESS;
1411 } /* }}} */
1412 
1413 int phpdbg_create_var_watchpoint(char *input, size_t len) {
1414 	if (phpdbg_rebuild_symtable() == FAILURE) {
1415 		return FAILURE;
1416 	}
1417 
1418 	return phpdbg_watchpoint_parse_symtables(input, len, phpdbg_create_simple_watchpoint);
1419 }
1420 
1421 PHPDBG_WATCH(recursive) /* {{{ */
1422 {
1423 	if (phpdbg_rebuild_symtable() == FAILURE) {
1424 		return SUCCESS;
1425 	}
1426 
1427 	switch (param->type) {
1428 		case STR_PARAM:
1429 			phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_recursive_watchpoint);
1430 			break;
1431 
1432 		phpdbg_default_switch_case();
1433 	}
1434 
1435 	return SUCCESS;
1436 } /* }}} */
1437 
1438 PHPDBG_WATCH(array) /* {{{ */
1439 {
1440 	if (phpdbg_rebuild_symtable() == FAILURE) {
1441 		return SUCCESS;
1442 	}
1443 
1444 	switch (param->type) {
1445 		case STR_PARAM:
1446 			phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_array_watchpoint);
1447 			break;
1448 
1449 		phpdbg_default_switch_case();
1450 	}
1451 
1452 	return SUCCESS;
1453 } /* }}} */
1454 
1455 
1456 void phpdbg_setup_watchpoints(void) {
1457 #if defined(_SC_PAGE_SIZE)
1458 	phpdbg_pagesize = sysconf(_SC_PAGE_SIZE);
1459 #elif defined(_SC_PAGESIZE)
1460 	phpdbg_pagesize = sysconf(_SC_PAGESIZE);
1461 #elif defined(_SC_NUTC_OS_PAGESIZE)
1462 	phpdbg_pagesize = sysconf(_SC_NUTC_OS_PAGESIZE);
1463 #else
1464 	phpdbg_pagesize = 4096; /* common pagesize */
1465 #endif
1466 
1467 	phpdbg_btree_init(&PHPDBG_G(watchpoint_tree), sizeof(void *) * 8);
1468 	phpdbg_btree_init(&PHPDBG_G(watch_HashTables), sizeof(void *) * 8);
1469 	zend_hash_init(&PHPDBG_G(watch_elements), 8, NULL, NULL, 0);
1470 	zend_hash_init(&PHPDBG_G(watch_collisions), 8, NULL, NULL, 0);
1471 	zend_hash_init(&PHPDBG_G(watch_recreation), 8, NULL, NULL, 0);
1472 	zend_hash_init(&PHPDBG_G(watch_free), 8, NULL, NULL, 0);
1473 
1474 	/* put these on a separate page, to avoid conflicts with other memory */
1475 	PHPDBG_G(watchlist_mem) = malloc(phpdbg_pagesize > sizeof(HashTable) ? phpdbg_pagesize : sizeof(HashTable));
1476 	PHPDBG_G(original_watchlist_mem) = PHPDBG_G(watchlist_mem);
1477 	zend_hash_init(PHPDBG_G(watchlist_mem), phpdbg_pagesize / (sizeof(Bucket) + sizeof(uint32_t)), NULL, NULL, 1);
1478 	PHPDBG_G(watchlist_mem_backup) = malloc(phpdbg_pagesize > sizeof(HashTable) ? phpdbg_pagesize : sizeof(HashTable));
1479 	zend_hash_init(PHPDBG_G(watchlist_mem_backup), phpdbg_pagesize / (sizeof(Bucket) + sizeof(uint32_t)), NULL, NULL, 1);
1480 
1481 	PHPDBG_G(watch_tmp) = NULL;
1482 	PHPDBG_G(watchpoint_hit) = false;
1483 
1484 #ifdef HAVE_USERFAULTFD_WRITEFAULT
1485 	PHPDBG_G(watch_userfaultfd) = syscall(SYS_userfaultfd, O_CLOEXEC);
1486 	if (PHPDBG_G(watch_userfaultfd) < 0) {
1487 		PHPDBG_G(watch_userfaultfd) = 0;
1488 	} else {
1489 		struct uffdio_api userfaultfd_features = {0};
1490 		userfaultfd_features.api = UFFD_API;
1491 		userfaultfd_features.features = UFFD_FEATURE_PAGEFAULT_FLAG_WP;
1492 		ioctl(PHPDBG_G(watch_userfaultfd), UFFDIO_API, &userfaultfd_features);
1493 		if (userfaultfd_features.features & UFFD_FEATURE_PAGEFAULT_FLAG_WP) {
1494 			pthread_create(&PHPDBG_G(watch_userfault_thread), NULL, phpdbg_watchpoint_userfaultfd_thread, ZEND_MODULE_GLOBALS_BULK(phpdbg));
1495 		} else {
1496 			PHPDBG_G(watch_userfaultfd) = 0;
1497 		}
1498 	}
1499 #endif
1500 }
1501 
1502 void phpdbg_destroy_watchpoints(void) {
1503 	phpdbg_watch_element *element;
1504 
1505 	/* unconditionally free all remaining elements to avoid memory leaks */
1506 	ZEND_HASH_MAP_FOREACH_PTR(&PHPDBG_G(watch_recreation), element) {
1507 		phpdbg_automatic_dequeue_free(element);
1508 	} ZEND_HASH_FOREACH_END();
1509 
1510 	/* 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. */
1511     phpdbg_purge_watchpoint_tree();
1512 
1513 #ifdef HAVE_USERFAULTFD_WRITEFAULT
1514 	if (PHPDBG_G(watch_userfaultfd)) {
1515 		pthread_cancel(PHPDBG_G(watch_userfault_thread));
1516 		close(PHPDBG_G(watch_userfaultfd));
1517 	}
1518 #endif
1519 
1520 	zend_hash_destroy(&PHPDBG_G(watch_elements)); PHPDBG_G(watch_elements).nNumOfElements = 0; /* phpdbg_watch_efree() is checking against this arrays size */
1521 	zend_hash_destroy(&PHPDBG_G(watch_recreation));
1522 	zend_hash_destroy(&PHPDBG_G(watch_free));
1523 	zend_hash_destroy(&PHPDBG_G(watch_collisions));
1524 	zend_hash_destroy(PHPDBG_G(original_watchlist_mem));
1525 	free(PHPDBG_G(original_watchlist_mem));
1526 	zend_hash_destroy(PHPDBG_G(watchlist_mem_backup));
1527 	free(PHPDBG_G(watchlist_mem_backup));
1528 }
1529 
1530 void phpdbg_purge_watchpoint_tree(void) {
1531 	phpdbg_btree_position pos;
1532 	phpdbg_btree_result *res;
1533 
1534 	pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), 0, -1);
1535 	while ((res = phpdbg_btree_next(&pos))) {
1536 		phpdbg_deactivate_watchpoint(res->ptr);
1537 	}
1538 }
1539