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