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