xref: /PHP-8.0/sapi/phpdbg/phpdbg_watch.c (revision 92c4b065)
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    | http://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 zend_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 		case WATCH_ON_ZVAL:
137 			return memcmp(oldPtr, newPtr, sizeof(zend_value) + sizeof(uint32_t) /* value + typeinfo */) != 0;
138 		case WATCH_ON_HASHTABLE:
139 			return zend_hash_num_elements(HT_PTR_HT(oldPtr)) != zend_hash_num_elements(HT_PTR_HT(newPtr));
140 		case WATCH_ON_REFCOUNTED:
141 			return memcmp(oldPtr, newPtr, sizeof(uint32_t) /* no zend_refcounted metadata info */) != 0;
142 		case WATCH_ON_STR:
143 			return memcmp(oldPtr, newPtr, *(size_t *) oldPtr + XtOffsetOf(zend_string, val) - XtOffsetOf(zend_string, len)) != 0;
144 		case WATCH_ON_HASHDATA:
145 			ZEND_UNREACHABLE();
146 	}
147 	return 0;
148 }
149 
phpdbg_print_watch_diff(phpdbg_watchtype type,zend_string * name,void * oldPtr,void * newPtr)150 void phpdbg_print_watch_diff(phpdbg_watchtype type, zend_string *name, void *oldPtr, void *newPtr) {
151 	int32_t elementDiff;
152 
153 	PHPDBG_G(watchpoint_hit) = 1;
154 
155 	phpdbg_notice("watchhit", "variable=\"%s\"", "Breaking on watchpoint %.*s", (int) ZSTR_LEN(name), ZSTR_VAL(name));
156 	phpdbg_xml("<watchdata %r>");
157 
158 	switch (type) {
159 		case WATCH_ON_BUCKET:
160 		case WATCH_ON_ZVAL:
161 			if (Z_REFCOUNTED_P((zval *) oldPtr)) {
162 				phpdbg_writeln("watchvalue", "type=\"old\" inaccessible=\"inaccessible\"", "Old value inaccessible or destroyed");
163 			} else if (Z_TYPE_P((zval *) oldPtr) == IS_INDIRECT) {
164 				phpdbg_writeln("watchvalue", "type=\"old\" inaccessible=\"inaccessible\"", "Old value inaccessible or destroyed (was indirect)");
165 			} else {
166 				phpdbg_out("Old value: ");
167 				phpdbg_xml("<watchvalue %r type=\"old\">");
168 				zend_print_flat_zval_r((zval *) oldPtr);
169 				phpdbg_xml("</watchvalue>");
170 				phpdbg_out("\n");
171 			}
172 
173 			while (Z_TYPE_P((zval *) newPtr) == IS_INDIRECT) {
174 				newPtr = Z_INDIRECT_P((zval *) newPtr);
175 			}
176 
177 			phpdbg_out("New value%s: ", Z_ISREF_P((zval *) newPtr) ? " (reference)" : "");
178 			phpdbg_xml("<watchvalue %r%s type=\"new\">", Z_ISREF_P((zval *) newPtr) ? " reference=\"reference\"" : "");
179 			zend_print_flat_zval_r((zval *) newPtr);
180 			phpdbg_xml("</watchvalue>");
181 			phpdbg_out("\n");
182 			break;
183 
184 		case WATCH_ON_HASHTABLE:
185 			elementDiff = zend_hash_num_elements(HT_PTR_HT(oldPtr)) - zend_hash_num_elements(HT_PTR_HT(newPtr));
186 			if (elementDiff > 0) {
187 				phpdbg_writeln("watchsize", "removed=\"%d\"", "%d elements were removed from the array", (int) elementDiff);
188 			} else if (elementDiff < 0) {
189 				phpdbg_writeln("watchsize", "added=\"%d\"", "%d elements were added to the array", (int) -elementDiff);
190 			}
191 			break;
192 
193 		case WATCH_ON_REFCOUNTED:
194 			phpdbg_writeln("watchrefcount", "type=\"old\" refcount=\"%d\"", "Old refcount: %d", GC_REFCOUNT((zend_refcounted *) oldPtr));
195 			phpdbg_writeln("watchrefcount", "type=\"new\" refcount=\"%d\"", "New refcount: %d", GC_REFCOUNT((zend_refcounted *) newPtr));
196 			break;
197 
198 		case WATCH_ON_STR:
199 			phpdbg_out("Old value: ");
200 			phpdbg_xml("<watchvalue %r type=\"old\">");
201 			zend_write((char *) oldPtr + XtOffsetOf(zend_string, val) - XtOffsetOf(zend_string, len), *(size_t *) oldPtr);
202 			phpdbg_xml("</watchvalue>");
203 			phpdbg_out("\n");
204 
205 			phpdbg_out("New value: ");
206 			phpdbg_xml("<watchvalue %r type=\"new\">");
207 			zend_write((char *) newPtr + XtOffsetOf(zend_string, val) - XtOffsetOf(zend_string, len), *(size_t *) newPtr);
208 			phpdbg_xml("</watchvalue>");
209 			phpdbg_out("\n");
210 			break;
211 
212 		case WATCH_ON_HASHDATA:
213 			ZEND_UNREACHABLE();
214 	}
215 
216 	phpdbg_xml("</watchdata>");
217 }
218 
219 /* ### LOW LEVEL WATCHPOINT HANDLING ### */
phpdbg_check_for_watchpoint(void * addr)220 static phpdbg_watchpoint_t *phpdbg_check_for_watchpoint(void *addr) {
221 	phpdbg_watchpoint_t *watch;
222 	phpdbg_btree_result *result = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), (zend_ulong) phpdbg_get_page_boundary(addr) + phpdbg_pagesize - 1);
223 
224 	if (result == NULL) {
225 		return NULL;
226 	}
227 
228 	watch = result->ptr;
229 
230 	/* check if that addr is in a mprotect()'ed memory area */
231 	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) {
232 		/* failure */
233 		return NULL;
234 	}
235 
236 	return watch;
237 }
238 
phpdbg_change_watchpoint_access(phpdbg_watchpoint_t * watch,int access)239 static void phpdbg_change_watchpoint_access(phpdbg_watchpoint_t *watch, int access) {
240 	/* pagesize is assumed to be in the range of 2^x */
241 	mprotect(phpdbg_get_page_boundary(watch->addr.ptr), phpdbg_get_total_page_size(watch->addr.ptr, watch->size), access);
242 }
243 
phpdbg_activate_watchpoint(phpdbg_watchpoint_t * watch)244 static inline void phpdbg_activate_watchpoint(phpdbg_watchpoint_t *watch) {
245 	phpdbg_change_watchpoint_access(watch, PROT_READ);
246 }
247 
phpdbg_deactivate_watchpoint(phpdbg_watchpoint_t * watch)248 static inline void phpdbg_deactivate_watchpoint(phpdbg_watchpoint_t *watch) {
249 	phpdbg_change_watchpoint_access(watch, PROT_READ | PROT_WRITE);
250 }
251 
252 /* 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 */
253 #ifdef _WIN32
phpdbg_watchpoint_segfault_handler(void * addr)254 int phpdbg_watchpoint_segfault_handler(void *addr) {
255 #else
256 int phpdbg_watchpoint_segfault_handler(siginfo_t *info, void *context) {
257 #endif
258 
259 	void *page = phpdbg_get_page_boundary(
260 #ifdef _WIN32
261 		addr
262 #else
263 		info->si_addr
264 #endif
265 	);
266 
267 	/* perhaps unnecessary, but check to be sure to not conflict with other segfault handlers */
268 	if (phpdbg_check_for_watchpoint(page) == NULL) {
269 		return FAILURE;
270 	}
271 
272 	/* re-enable writing */
273 	mprotect(page, phpdbg_pagesize, PROT_READ | PROT_WRITE);
274 
275 	zend_hash_index_add_empty_element(PHPDBG_G(watchlist_mem), (zend_ulong) page);
276 
277 	return SUCCESS;
278 }
279 
280 /* ### REGISTER WATCHPOINT ### To be used only by watch element and collision managers ### */
281 static inline void phpdbg_store_watchpoint_btree(phpdbg_watchpoint_t *watch) {
282 	phpdbg_btree_result *res;
283 	ZEND_ASSERT((res = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr)) == NULL || res->ptr == watch);
284 	phpdbg_btree_insert(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr, watch);
285 }
286 
287 static inline void phpdbg_remove_watchpoint_btree(phpdbg_watchpoint_t *watch) {
288 	phpdbg_btree_delete(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr);
289 }
290 
291 /* ### SET WATCHPOINT ADDR ### To be used only by watch element and collision managers ### */
292 void phpdbg_set_addr_watchpoint(void *addr, size_t size, phpdbg_watchpoint_t *watch) {
293 	watch->addr.ptr = addr;
294 	watch->size = size;
295 	watch->ref = NULL;
296 	watch->coll = NULL;
297 	zend_hash_init(&watch->elements, 8, brml, NULL, 0);
298 }
299 
300 void phpdbg_set_zval_watchpoint(zval *zv, phpdbg_watchpoint_t *watch) {
301 	phpdbg_set_addr_watchpoint(zv, sizeof(zval) - sizeof(uint32_t), watch);
302 	watch->type = WATCH_ON_ZVAL;
303 }
304 
305 void phpdbg_set_bucket_watchpoint(Bucket *bucket, phpdbg_watchpoint_t *watch) {
306 	phpdbg_set_addr_watchpoint(bucket, sizeof(Bucket), watch);
307 	watch->type = WATCH_ON_BUCKET;
308 }
309 
310 void phpdbg_set_ht_watchpoint(HashTable *ht, phpdbg_watchpoint_t *watch) {
311 	phpdbg_set_addr_watchpoint(((char *) ht) + HT_WATCH_OFFSET, sizeof(HashTable) - HT_WATCH_OFFSET, watch);
312 	watch->type = WATCH_ON_HASHTABLE;
313 }
314 
315 void phpdbg_watch_backup_data(phpdbg_watchpoint_t *watch) {
316 	switch (watch->type) {
317 		case WATCH_ON_BUCKET:
318 		case WATCH_ON_ZVAL:
319 		case WATCH_ON_REFCOUNTED:
320 			memcpy(&watch->backup, watch->addr.ptr, watch->size);
321 			break;
322 		case WATCH_ON_STR:
323 			if (watch->backup.str) {
324 				zend_string_release(watch->backup.str);
325 			}
326 			watch->backup.str = zend_string_init((char *) watch->addr.ptr + XtOffsetOf(zend_string, val) - XtOffsetOf(zend_string, len), *(size_t *) watch->addr.ptr, 1);
327 			GC_MAKE_PERSISTENT_LOCAL(watch->backup.str);
328 			break;
329 		case WATCH_ON_HASHTABLE:
330 			memcpy((char *) &watch->backup + HT_WATCH_OFFSET, watch->addr.ptr, watch->size);
331 		case WATCH_ON_HASHDATA:
332 			break;
333 	}
334 }
335 
336 /* ### MANAGE WATCH COLLISIONS ### To be used only by watch element manager and memory differ ### */
337 /* watch collisions are responsible for having only one watcher on a given refcounted/refval and having a mapping back to the parent zvals */
338 void phpdbg_delete_watch_collision(phpdbg_watchpoint_t *watch) {
339 	phpdbg_watch_collision *coll;
340 	if ((coll = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref))) {
341 		zend_hash_index_del(&coll->parents, (zend_ulong) watch);
342 		if (zend_hash_num_elements(&coll->parents) == 0) {
343 			phpdbg_deactivate_watchpoint(&coll->ref);
344 			phpdbg_remove_watchpoint_btree(&coll->ref);
345 
346 			if (coll->ref.type == WATCH_ON_ZVAL) {
347 				phpdbg_delete_watch_collision(&coll->ref);
348 			} else if (coll->reference.addr.ptr) {
349 				phpdbg_deactivate_watchpoint(&coll->reference);
350 				phpdbg_remove_watchpoint_btree(&coll->reference);
351 				phpdbg_delete_watch_collision(&coll->reference);
352 				if (coll->reference.type == WATCH_ON_STR) {
353 					zend_string_release(coll->reference.backup.str);
354 				}
355 			}
356 
357 			zend_hash_index_del(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref);
358 			zend_hash_destroy(&coll->parents);
359 			efree(coll);
360 		}
361 	}
362 }
363 
364 void phpdbg_update_watch_ref(phpdbg_watchpoint_t *watch) {
365 	phpdbg_watch_collision *coll;
366 
367 	ZEND_ASSERT(watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET);
368 	if (Z_REFCOUNTED_P(watch->addr.zv)) {
369 		if (Z_COUNTED_P(watch->addr.zv) == watch->ref) {
370 			return;
371 		}
372 
373 		if (watch->ref != NULL) {
374 			phpdbg_delete_watch_collision(watch);
375 		}
376 
377 		watch->ref = Z_COUNTED_P(watch->addr.zv);
378 
379 		if (!(coll = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref))) {
380 			coll = emalloc(sizeof(*coll));
381 			coll->ref.type = WATCH_ON_REFCOUNTED;
382 			phpdbg_set_addr_watchpoint(Z_COUNTED_P(watch->addr.zv), sizeof(uint32_t), &coll->ref);
383 			coll->ref.coll = coll;
384 			phpdbg_store_watchpoint_btree(&coll->ref);
385 			phpdbg_activate_watchpoint(&coll->ref);
386 			phpdbg_watch_backup_data(&coll->ref);
387 
388 			if (Z_ISREF_P(watch->addr.zv)) {
389 				phpdbg_set_zval_watchpoint(Z_REFVAL_P(watch->addr.zv), &coll->reference);
390 				coll->reference.coll = coll;
391 				phpdbg_update_watch_ref(&coll->reference);
392 				phpdbg_store_watchpoint_btree(&coll->reference);
393 				phpdbg_activate_watchpoint(&coll->reference);
394 				phpdbg_watch_backup_data(&coll->reference);
395 			} else if (Z_TYPE_P(watch->addr.zv) == IS_STRING) {
396 				coll->reference.type = WATCH_ON_STR;
397 				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);
398 				coll->reference.coll = coll;
399 				phpdbg_store_watchpoint_btree(&coll->reference);
400 				phpdbg_activate_watchpoint(&coll->reference);
401 				coll->reference.backup.str = NULL;
402 				phpdbg_watch_backup_data(&coll->reference);
403 			} else {
404 				coll->reference.addr.ptr = NULL;
405 			}
406 
407 			zend_hash_init(&coll->parents, 8, shitty stupid parameter, NULL, 0);
408 			zend_hash_index_add_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref, coll);
409 		}
410 		zend_hash_index_add_ptr(&coll->parents, (zend_long) watch, watch);
411 	} else if (Z_TYPE_P(watch->addr.zv) == IS_INDIRECT) {
412 		if ((zend_refcounted *) Z_INDIRECT_P(watch->addr.zv) == watch->ref) {
413 			return;
414 		}
415 
416 		if (watch->ref != NULL) {
417 			phpdbg_delete_watch_collision(watch);
418 		}
419 
420 		watch->ref = (zend_refcounted *) Z_INDIRECT_P(watch->addr.zv);
421 
422 		if (!(coll = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref))) {
423 			coll = emalloc(sizeof(*coll));
424 			phpdbg_set_zval_watchpoint(Z_INDIRECT_P(watch->addr.zv), &coll->ref);
425 			coll->ref.coll = coll;
426 			phpdbg_update_watch_ref(&coll->ref);
427 			phpdbg_store_watchpoint_btree(&coll->ref);
428 			phpdbg_activate_watchpoint(&coll->ref);
429 			phpdbg_watch_backup_data(&coll->ref);
430 
431 			zend_hash_init(&coll->parents, 8, shitty stupid parameter, NULL, 0);
432 			zend_hash_index_add_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref, coll);
433 		}
434 		zend_hash_index_add_ptr(&coll->parents, (zend_long) watch, watch);
435 	} else if (watch->ref) {
436 		phpdbg_delete_watch_collision(watch);
437 		watch->ref = NULL;
438 	}
439 }
440 
441 /* ### MANAGE WATCH ELEMENTS ### */
442 /* watchpoints must be unique per element. Only one watchpoint may point to one element. But many elements may point to one watchpoint. */
443 void phpdbg_recurse_watch_element(phpdbg_watch_element *element);
444 void phpdbg_remove_watch_element_recursively(phpdbg_watch_element *element);
445 void phpdbg_free_watch_element(phpdbg_watch_element *element);
446 void phpdbg_remove_watchpoint(phpdbg_watchpoint_t *watch);
447 void phpdbg_watch_parent_ht(phpdbg_watch_element *element);
448 
449 phpdbg_watch_element *phpdbg_add_watch_element(phpdbg_watchpoint_t *watch, phpdbg_watch_element *element) {
450 	phpdbg_btree_result *res;
451 	if ((res = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr)) == NULL) {
452 		phpdbg_watchpoint_t *mem = emalloc(sizeof(*mem));
453 		*mem = *watch;
454 		watch = mem;
455 		phpdbg_store_watchpoint_btree(watch);
456 		if (watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) {
457 			phpdbg_update_watch_ref(watch);
458 		}
459 		phpdbg_activate_watchpoint(watch);
460 		phpdbg_watch_backup_data(watch);
461 	} else {
462 		phpdbg_watch_element *old_element;
463 		watch = res->ptr;
464 		if ((old_element = zend_hash_find_ptr(&watch->elements, element->str))) {
465 			phpdbg_free_watch_element(element);
466 			return old_element;
467 		}
468 	}
469 
470 	element->watch = watch;
471 	zend_hash_add_ptr(&watch->elements, element->str, element);
472 
473 	if (element->flags & PHPDBG_WATCH_RECURSIVE) {
474 		phpdbg_recurse_watch_element(element);
475 	}
476 
477 	return element;
478 }
479 
480 phpdbg_watch_element *phpdbg_add_bucket_watch_element(Bucket *bucket, phpdbg_watch_element *element) {
481 	phpdbg_watchpoint_t watch;
482 	phpdbg_set_bucket_watchpoint(bucket, &watch);
483 	element = phpdbg_add_watch_element(&watch, element);
484 	phpdbg_watch_parent_ht(element);
485 	return element;
486 }
487 
488 phpdbg_watch_element *phpdbg_add_ht_watch_element(zval *zv, phpdbg_watch_element *element) {
489 	phpdbg_watchpoint_t watch;
490 	HashTable *ht = HT_FROM_ZVP(zv);
491 
492 	if (!ht) {
493 		return NULL;
494 	}
495 
496 	element->flags |= Z_TYPE_P(zv) == IS_ARRAY ? PHPDBG_WATCH_ARRAY : PHPDBG_WATCH_OBJECT;
497 	phpdbg_set_ht_watchpoint(ht, &watch);
498 	return phpdbg_add_watch_element(&watch, element);
499 }
500 
501 zend_bool phpdbg_is_recursively_watched(void *ptr, phpdbg_watch_element *element) {
502 	phpdbg_watch_element *next = element;
503 	do {
504 		element = next;
505 		if (element->watch->addr.ptr == ptr) {
506 			return 1;
507 		}
508 		next = element->parent;
509 	} while (!(element->flags & PHPDBG_WATCH_RECURSIVE_ROOT));
510 
511 	return 0;
512 }
513 
514 void phpdbg_add_recursive_watch_from_ht(phpdbg_watch_element *element, zend_long idx, zend_string *str, zval *zv) {
515 	phpdbg_watch_element *child;
516 	if (phpdbg_is_recursively_watched(zv, element)) {
517 		return;
518 	}
519 
520 	child = emalloc(sizeof(*child));
521 	child->flags = PHPDBG_WATCH_RECURSIVE;
522 	if (str) {
523 		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)));
524 	} else {
525 		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);
526 	}
527 	if (!str) {
528 		str = zend_long_to_str(idx); // TODO: hack, use proper int handling for name in parent
529 	} else { str = zend_string_copy(str); }
530 	child->name_in_parent = str;
531 	child->parent = element;
532 	child->child = NULL;
533 	child->parent_container = HT_WATCH_HT(element->watch);
534 	zend_hash_add_ptr(&element->child_container, child->str, child);
535 	phpdbg_add_bucket_watch_element((Bucket *) zv, child);
536 }
537 
538 void phpdbg_recurse_watch_element(phpdbg_watch_element *element) {
539 	phpdbg_watch_element *child;
540 	zval *zv;
541 
542 	if (element->watch->type == WATCH_ON_ZVAL || element->watch->type == WATCH_ON_BUCKET) {
543 		zv = element->watch->addr.zv;
544 		while (Z_TYPE_P(zv) == IS_INDIRECT) {
545 			zv = Z_INDIRECT_P(zv);
546 		}
547 		ZVAL_DEREF(zv);
548 
549 		if (element->child) {
550 			phpdbg_remove_watch_element_recursively(element->child);
551 		}
552 
553 		if ((Z_TYPE_P(zv) != IS_ARRAY && Z_TYPE_P(zv) != IS_OBJECT)
554 		    || phpdbg_is_recursively_watched(HT_WATCH_OFFSET + (char *) HT_FROM_ZVP(zv), element)) {
555 			if (element->child) {
556 				phpdbg_free_watch_element(element->child);
557 				element->child = NULL;
558 			}
559 			return;
560 		}
561 
562 		if (element->child) {
563 			child = element->child;
564 		} else {
565 			child = emalloc(sizeof(*child));
566 			child->flags = PHPDBG_WATCH_RECURSIVE;
567 			child->str = strpprintf(0, "%.*s[]", (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str));
568 			child->name_in_parent = NULL;
569 			child->parent = element;
570 			child->child = NULL;
571 			element->child = child;
572 		}
573 		zend_hash_init(&child->child_container, 8, NULL, NULL, 0);
574 		phpdbg_add_ht_watch_element(zv, child);
575 	} else if (zend_hash_num_elements(&element->child_container) == 0) {
576 		zend_string *str;
577 		zend_long idx;
578 
579 		ZEND_ASSERT(element->watch->type == WATCH_ON_HASHTABLE);
580 		ZEND_HASH_FOREACH_KEY_VAL(HT_WATCH_HT(element->watch), idx, str, zv) {
581 			phpdbg_add_recursive_watch_from_ht(element, idx, str, zv);
582 		} ZEND_HASH_FOREACH_END();
583 	}
584 }
585 
586 void phpdbg_watch_parent_ht(phpdbg_watch_element *element) {
587 	if (element->watch->type == WATCH_ON_BUCKET) {
588 		phpdbg_btree_result *res;
589 		HashPosition pos;
590 		phpdbg_watch_ht_info *hti;
591 		ZEND_ASSERT(element->parent_container);
592 		if (!(res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) element->parent_container))) {
593 			hti = emalloc(sizeof(*hti));
594 			hti->ht = element->parent_container;
595 
596 			zend_hash_init(&hti->watches, 0, grrrrr, ZVAL_PTR_DTOR, 0);
597 			phpdbg_btree_insert(&PHPDBG_G(watch_HashTables), (zend_ulong) hti->ht, hti);
598 
599 			phpdbg_set_addr_watchpoint(HT_GET_DATA_ADDR(hti->ht), HT_HASH_SIZE(hti->ht->nTableMask), &hti->hash_watch);
600 			hti->hash_watch.type = WATCH_ON_HASHDATA;
601 			phpdbg_store_watchpoint_btree(&hti->hash_watch);
602 			phpdbg_activate_watchpoint(&hti->hash_watch);
603 		} else {
604 			hti = (phpdbg_watch_ht_info *) res->ptr;
605 		}
606 
607 		zend_hash_internal_pointer_end_ex(hti->ht, &pos);
608 		hti->last = hti->ht->arData + pos;
609 		hti->last_str = hti->last->key;
610 		hti->last_idx = hti->last->h;
611 
612 		zend_hash_add_ptr(&hti->watches, element->name_in_parent, element);
613 	}
614 }
615 
616 void phpdbg_unwatch_parent_ht(phpdbg_watch_element *element) {
617 	if (element->watch->type == WATCH_ON_BUCKET) {
618 		phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) element->parent_container);
619 		ZEND_ASSERT(element->parent_container);
620 		if (res) {
621 			phpdbg_watch_ht_info *hti = res->ptr;
622 
623 			if (zend_hash_num_elements(&hti->watches) == 1) {
624 				zend_hash_destroy(&hti->watches);
625 				phpdbg_btree_delete(&PHPDBG_G(watch_HashTables), (zend_ulong) hti->ht);
626 				phpdbg_deactivate_watchpoint(&hti->hash_watch);
627 				phpdbg_remove_watchpoint_btree(&hti->hash_watch);
628 				efree(hti);
629 			} else {
630 				zend_hash_del(&hti->watches, element->name_in_parent);
631 			}
632 		}
633 	}
634 }
635 
636 /* ### DE/QUEUE WATCH ELEMENTS ### to be used by watch element manager only */
637 /* 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 */
638 
639 void phpdbg_dissociate_watch_element(phpdbg_watch_element *element, phpdbg_watch_element *until);
640 void phpdbg_free_watch_element_tree(phpdbg_watch_element *element);
641 
642 void phpdbg_queue_element_for_recreation(phpdbg_watch_element *element) {
643 	/* store lowermost element */
644 	phpdbg_watch_element *prev;
645 
646 	if ((prev = zend_hash_find_ptr(&PHPDBG_G(watch_recreation), element->str))) {
647 		phpdbg_watch_element *child = prev;
648 		do {
649 			if (child == element) {
650 				return;
651 			}
652 			child = child->child;
653 		} while (child);
654 	}
655 	zend_hash_update_ptr(&PHPDBG_G(watch_recreation), element->str, element);
656 
657 	/* dissociate from watchpoint to avoid dangling memory watches */
658 	phpdbg_dissociate_watch_element(element, prev);
659 
660 	if (!element->parent) {
661 		/* 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) */
662 		zend_hash_index_add_empty_element(&PHPDBG_G(watch_free), (zend_ulong) element->parent_container);
663 	}
664 }
665 
666 zend_bool phpdbg_try_readding_watch_element(zval *parent, phpdbg_watch_element *element) {
667 	zval *zv;
668 	HashTable *ht = HT_FROM_ZVP(parent);
669 
670 	if (!ht) {
671 		return 0;
672 	} else if (element->flags & (PHPDBG_WATCH_ARRAY | PHPDBG_WATCH_OBJECT)) {
673 		char *htPtr = ((char *) ht) + HT_WATCH_OFFSET;
674 		char *oldPtr = ((char *) &element->backup.ht) + HT_WATCH_OFFSET;
675 		if (phpdbg_check_watch_diff(WATCH_ON_HASHTABLE, oldPtr, htPtr)) {
676 			phpdbg_print_watch_diff(WATCH_ON_HASHTABLE, element->str, oldPtr, htPtr);
677 		}
678 
679 		phpdbg_add_ht_watch_element(parent, element);
680 	} else if ((zv = zend_symtable_find(ht, element->name_in_parent))) {
681 		if (element->flags & PHPDBG_WATCH_IMPLICIT) {
682 			zval *next = zv;
683 
684 			while (Z_TYPE_P(next) == IS_INDIRECT) {
685 				next = Z_INDIRECT_P(next);
686 			}
687 			if (Z_ISREF_P(next)) {
688 				next = Z_REFVAL_P(next);
689 			}
690 
691 			if (!phpdbg_try_readding_watch_element(next, element->child)) {
692 				return 0;
693 			}
694 		} else if (phpdbg_check_watch_diff(WATCH_ON_ZVAL, &element->backup.zv, zv)) {
695 			phpdbg_print_watch_diff(WATCH_ON_ZVAL, element->str, &element->backup.zv, zv);
696 		}
697 
698 		element->parent_container = ht;
699 		phpdbg_add_bucket_watch_element((Bucket *) zv, element);
700 		phpdbg_watch_parent_ht(element);
701 	} else {
702 		return 0;
703 	}
704 
705 	return 1;
706 }
707 
708 void phpdbg_automatic_dequeue_free(phpdbg_watch_element *element) {
709 	phpdbg_watch_element *child = element;
710 	while (child->child && !(child->flags & PHPDBG_WATCH_RECURSIVE_ROOT)) {
711 		child = child->child;
712 	}
713 	PHPDBG_G(watchpoint_hit) = 1;
714 	if (zend_hash_index_del(&PHPDBG_G(watch_elements), child->id) == SUCCESS) {
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 	}
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 = NULL;
1017 			zval *new;
1018 
1019 			ZEND_HASH_FOREACH_PTR(&watch->elements, element) {
1020 				break;
1021 			} ZEND_HASH_FOREACH_END();
1022 
1023 			ZEND_ASSERT(element); /* elements must be non-empty */
1024 			new = zend_symtable_find(element->parent_container, element->name_in_parent);
1025 
1026 			if (!new) {
1027 				/* dequeuing will take care of appropriate notification about removal */
1028 				phpdbg_remove_watchpoint(watch);
1029 				return;
1030 			}
1031 
1032 			phpdbg_deactivate_watchpoint(watch);
1033 			phpdbg_remove_watchpoint_btree(watch);
1034 			watch->addr.zv = new;
1035 			phpdbg_store_watchpoint_btree(watch);
1036 			phpdbg_activate_watchpoint(watch);
1037 
1038 			if (!phpdbg_check_watch_diff(WATCH_ON_ZVAL, &watch->backup.bucket.val, watch->addr.ptr)) {
1039 				phpdbg_watch_backup_data(watch);
1040 				return;
1041 			}
1042 		} else if (Z_TYPE_P(watch->addr.zv) == IS_UNDEF) {
1043 			/* dequeuing will take care of appropriate notification about removal */
1044 			phpdbg_remove_watchpoint(watch);
1045 			return;
1046 		}
1047 	}
1048 
1049 	name = phpdbg_watchpoint_change_collision_name(watch);
1050 
1051 	if (name) {
1052 		phpdbg_print_watch_diff(watch->type, name, comparePtr, watch->addr.ptr);
1053 		zend_string_release(name);
1054 	}
1055 
1056 	if (watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) {
1057 		phpdbg_watch_element *element;
1058 		phpdbg_update_watch_ref(watch);
1059 		ZEND_HASH_FOREACH_PTR(&watch->elements, element) {
1060 			if (element->flags & PHPDBG_WATCH_RECURSIVE) {
1061 				phpdbg_recurse_watch_element(element);
1062 			}
1063 		} ZEND_HASH_FOREACH_END();
1064 	}
1065 
1066 	phpdbg_watch_backup_data(watch);
1067 }
1068 
1069 void phpdbg_reenable_memory_watches(void) {
1070 	zend_ulong page;
1071 	phpdbg_btree_result *res;
1072 	phpdbg_watchpoint_t *watch;
1073 
1074 	ZEND_HASH_FOREACH_NUM_KEY(PHPDBG_G(watchlist_mem), page) {
1075 		/* Disable writing again if there are any watchers on that page */
1076 		res = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), page + phpdbg_pagesize - 1);
1077 		if (res) {
1078 			watch = res->ptr;
1079 			if ((char *) page < (char *) watch->addr.ptr + watch->size) {
1080 				mprotect((void *) page, phpdbg_pagesize, PROT_READ);
1081 			}
1082 		}
1083 	} ZEND_HASH_FOREACH_END();
1084 	zend_hash_clean(PHPDBG_G(watchlist_mem));
1085 }
1086 
1087 int phpdbg_print_changed_zvals(void) {
1088 	int ret;
1089 	zend_ulong page;
1090 	phpdbg_watchpoint_t *watch;
1091 	phpdbg_btree_result *res;
1092 	HashTable *mem_list = NULL;
1093 
1094 	if (zend_hash_num_elements(&PHPDBG_G(watch_elements)) == 0) {
1095 		return FAILURE;
1096 	}
1097 
1098 	if (zend_hash_num_elements(PHPDBG_G(watchlist_mem)) > 0) {
1099 		/* we must not add elements to the hashtable while iterating over it (resize => read into freed memory) */
1100 		mem_list = PHPDBG_G(watchlist_mem);
1101 		PHPDBG_G(watchlist_mem) = PHPDBG_G(watchlist_mem_backup);
1102 
1103 		ZEND_HASH_FOREACH_NUM_KEY(mem_list, page) {
1104 			phpdbg_btree_position pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), page, page + phpdbg_pagesize);
1105 
1106 			while ((res = phpdbg_btree_next(&pos))) {
1107 				watch = res->ptr;
1108 				phpdbg_check_watchpoint(watch);
1109 			}
1110 			if ((res = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), page - 1))) {
1111 				watch = res->ptr;
1112 				if ((char *) page < (char *) watch->addr.ptr + watch->size) {
1113 					phpdbg_check_watchpoint(watch);
1114 				}
1115 			}
1116 		} ZEND_HASH_FOREACH_END();
1117 	}
1118 
1119 	phpdbg_dequeue_elements_for_recreation();
1120 
1121 	phpdbg_reenable_memory_watches();
1122 
1123 	if (mem_list) {
1124 		PHPDBG_G(watchlist_mem) = mem_list;
1125 		phpdbg_reenable_memory_watches();
1126 	}
1127 
1128 	ret = PHPDBG_G(watchpoint_hit) ? SUCCESS : FAILURE;
1129 	PHPDBG_G(watchpoint_hit) = 0;
1130 
1131 	return ret;
1132 }
1133 
1134 void phpdbg_watch_efree(void *ptr) {
1135 	phpdbg_btree_result *result;
1136 
1137 	/* only do expensive checks if there are any watches at all */
1138 	if (zend_hash_num_elements(&PHPDBG_G(watch_elements))) {
1139 		if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) ptr))) {
1140 			phpdbg_watchpoint_t *watch = result->ptr;
1141 			if (watch->type != WATCH_ON_HASHDATA) {
1142 				phpdbg_remove_watchpoint(watch);
1143 			} else {
1144 				/* remove all linked watchpoints, they will be dissociated from their elements */
1145 				phpdbg_watch_element *element;
1146 				phpdbg_watch_ht_info *hti = (phpdbg_watch_ht_info *) watch;
1147 
1148 				ZEND_HASH_FOREACH_PTR(&hti->watches, element) {
1149 					zend_ulong num = zend_hash_num_elements(&hti->watches);
1150 					phpdbg_remove_watchpoint(element->watch);
1151 					if (num == 1) { /* prevent access into freed memory */
1152 						break;
1153 					}
1154 				} ZEND_HASH_FOREACH_END();
1155 			}
1156 		}
1157 
1158 		/* special case watchpoints as they aren't on ptr but on ptr + HT_WATCH_OFFSET */
1159 		if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), HT_WATCH_OFFSET + (zend_ulong) ptr))) {
1160 			phpdbg_watchpoint_t *watch = result->ptr;
1161 			if (watch->type == WATCH_ON_HASHTABLE) {
1162 				phpdbg_remove_watchpoint(watch);
1163 			}
1164 		}
1165 
1166 		zend_hash_index_del(&PHPDBG_G(watch_free), (zend_ulong) ptr);
1167 	}
1168 
1169 	if (PHPDBG_G(original_free_function)) {
1170 		PHPDBG_G(original_free_function)(ptr);
1171 	}
1172 }
1173 
1174 /* ### USER API ### */
1175 void phpdbg_list_watchpoints(void) {
1176 	phpdbg_watch_element *element;
1177 
1178 	phpdbg_xml("<watchlist %r>");
1179 
1180 	ZEND_HASH_FOREACH_PTR(&PHPDBG_G(watch_elements), element) {
1181 		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");
1182 	} ZEND_HASH_FOREACH_END();
1183 
1184 	phpdbg_xml("</watchlist>");
1185 }
1186 
1187 static int phpdbg_create_simple_watchpoint(zval *zv, phpdbg_watch_element *element) {
1188 	element->flags = PHPDBG_WATCH_SIMPLE;
1189 	phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1190 	return SUCCESS;
1191 }
1192 
1193 static int phpdbg_create_array_watchpoint(zval *zv, phpdbg_watch_element *element) {
1194 	phpdbg_watch_element *new;
1195 	zend_string *str;
1196 	zval *orig_zv = zv;
1197 
1198 	ZVAL_DEREF(zv);
1199 	if (Z_TYPE_P(zv) != IS_ARRAY && Z_TYPE_P(zv) != IS_OBJECT) {
1200 		return FAILURE;
1201 	}
1202 
1203 	new = ecalloc(1, sizeof(phpdbg_watch_element));
1204 
1205 	str = strpprintf(0, "%.*s[]", (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str));
1206 	zend_string_release(element->str);
1207 	element->str = str;
1208 	element->flags = PHPDBG_WATCH_IMPLICIT;
1209 	phpdbg_add_bucket_watch_element((Bucket *) orig_zv, element);
1210 	element->child = new;
1211 
1212 	new->flags = PHPDBG_WATCH_SIMPLE;
1213 	new->str = zend_string_copy(str);
1214 	new->parent = element;
1215 	phpdbg_add_ht_watch_element(zv, new);
1216 	return SUCCESS;
1217 }
1218 
1219 static int phpdbg_create_recursive_watchpoint(zval *zv, phpdbg_watch_element *element) {
1220 	element->flags = PHPDBG_WATCH_RECURSIVE | PHPDBG_WATCH_RECURSIVE_ROOT;
1221 	element->child = NULL;
1222 	phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1223 	return SUCCESS;
1224 }
1225 
1226 typedef struct { int (*callback)(zval *zv, phpdbg_watch_element *); zend_string *str; } phpdbg_watch_parse_struct;
1227 
1228 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) {
1229 	int ret;
1230 	phpdbg_watch_element *element = ecalloc(1, sizeof(phpdbg_watch_element));
1231 	element->str = zend_string_init(name, namelen, 0);
1232 	element->name_in_parent = zend_string_init(key, keylen, 0);
1233 	element->parent_container = parent;
1234 	element->parent = PHPDBG_G(watch_tmp);
1235 	element->child = NULL;
1236 
1237 	ret = info->callback(zv, element);
1238 
1239 	efree(name);
1240 	efree(key);
1241 
1242 	if (ret != SUCCESS) {
1243 		phpdbg_remove_watch_element(element);
1244 	} else {
1245 		if (PHPDBG_G(watch_tmp)) {
1246 			PHPDBG_G(watch_tmp)->child = element;
1247 		}
1248 
1249 		if (element->child) {
1250 			element = element->child;
1251 		}
1252 
1253 		/* work around missing API for extending an array with a new element, and getting its index */
1254 		zend_hash_next_index_insert_ptr(&PHPDBG_G(watch_elements), element);
1255 		element->id = PHPDBG_G(watch_elements).nNextFreeElement - 1;
1256 
1257 		phpdbg_notice("watchadd", "index=\"%d\" variable=\"%.*s\"", "Added%s watchpoint #%u for %.*s", (element->flags & PHPDBG_WATCH_RECURSIVE_ROOT) ? " recursive" : "", element->id, (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str));
1258 	}
1259 
1260 	PHPDBG_G(watch_tmp) = NULL;
1261 
1262 	return ret;
1263 }
1264 
1265 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) {
1266 	return phpdbg_parse_variable_with_arg(input, len, parent, i, (phpdbg_parse_var_with_arg_func) phpdbg_watchpoint_parse_wrapper, NULL, 0, info);
1267 }
1268 
1269 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) {
1270 	phpdbg_watch_element *element;
1271 
1272 	/* do not install watch elements for references */
1273 	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) {
1274 		efree(name);
1275 		efree(key);
1276 		return SUCCESS;
1277 	}
1278 
1279 	element = ecalloc(1, sizeof(phpdbg_watch_element));
1280 	element->flags = PHPDBG_WATCH_IMPLICIT;
1281 	element->str = zend_string_copy(info->str);
1282 	element->name_in_parent = zend_string_init(key, keylen, 0);
1283 	element->parent_container = parent;
1284 	element->parent = PHPDBG_G(watch_tmp);
1285 	element = phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1286 
1287 	efree(name);
1288 	efree(key);
1289 
1290 	if (PHPDBG_G(watch_tmp)) {
1291 		PHPDBG_G(watch_tmp)->child = element;
1292 	}
1293 	PHPDBG_G(watch_tmp) = element;
1294 
1295 	return SUCCESS;
1296 }
1297 
1298 static int phpdbg_watchpoint_parse_symtables(char *input, size_t len, int (*callback)(zval *, phpdbg_watch_element *)) {
1299 	zend_class_entry *scope = zend_get_executed_scope();
1300 	phpdbg_watch_parse_struct info;
1301 	int ret;
1302 
1303 	if (scope && len >= 5 && !memcmp("$this", input, 5)) {
1304 		zend_hash_str_add(EG(current_execute_data)->symbol_table, ZEND_STRL("this"), &EG(current_execute_data)->This);
1305 	}
1306 
1307 	if (callback == phpdbg_create_array_watchpoint) {
1308 		info.str = strpprintf(0, "%.*s[]", (int) len, input);
1309 	} else {
1310 		info.str = zend_string_init(input, len, 0);
1311 	}
1312 	info.callback = callback;
1313 
1314 	if (phpdbg_is_auto_global(input, len) && phpdbg_watchpoint_parse_input(input, len, &EG(symbol_table), 0, &info, 1) != FAILURE) {
1315 		zend_string_release(info.str);
1316 		return SUCCESS;
1317 	}
1318 
1319 	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);
1320 
1321 	zend_string_release(info.str);
1322 	return ret;
1323 }
1324 
1325 PHPDBG_WATCH(delete) /* {{{ */
1326 {
1327 	phpdbg_watch_element *element;
1328 	switch (param->type) {
1329 		case NUMERIC_PARAM:
1330 			if ((element = zend_hash_index_find_ptr(&PHPDBG_G(watch_elements), param->num))) {
1331 				phpdbg_remove_watch_element(element);
1332 				phpdbg_notice("watchdelete", "variable=\"%.*s\"", "Removed watchpoint %d", (int) param->num);
1333 			} else {
1334 				phpdbg_error("watchdelete", "type=\"nowatch\"", "Nothing was deleted, no corresponding watchpoint found");
1335 			}
1336 			break;
1337 
1338 		phpdbg_default_switch_case();
1339 	}
1340 
1341 	return SUCCESS;
1342 } /* }}} */
1343 
1344 int phpdbg_create_var_watchpoint(char *input, size_t len) {
1345 	if (phpdbg_rebuild_symtable() == FAILURE) {
1346 		return FAILURE;
1347 	}
1348 
1349 	return phpdbg_watchpoint_parse_symtables(input, len, phpdbg_create_simple_watchpoint);
1350 }
1351 
1352 PHPDBG_WATCH(recursive) /* {{{ */
1353 {
1354 	if (phpdbg_rebuild_symtable() == FAILURE) {
1355 		return SUCCESS;
1356 	}
1357 
1358 	switch (param->type) {
1359 		case STR_PARAM:
1360 			phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_recursive_watchpoint);
1361 			break;
1362 
1363 		phpdbg_default_switch_case();
1364 	}
1365 
1366 	return SUCCESS;
1367 } /* }}} */
1368 
1369 PHPDBG_WATCH(array) /* {{{ */
1370 {
1371 	if (phpdbg_rebuild_symtable() == FAILURE) {
1372 		return SUCCESS;
1373 	}
1374 
1375 	switch (param->type) {
1376 		case STR_PARAM:
1377 			phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_array_watchpoint);
1378 			break;
1379 
1380 		phpdbg_default_switch_case();
1381 	}
1382 
1383 	return SUCCESS;
1384 } /* }}} */
1385 
1386 
1387 void phpdbg_setup_watchpoints(void) {
1388 #if defined(_SC_PAGE_SIZE)
1389 	phpdbg_pagesize = sysconf(_SC_PAGE_SIZE);
1390 #elif defined(_SC_PAGESIZE)
1391 	phpdbg_pagesize = sysconf(_SC_PAGESIZE);
1392 #elif defined(_SC_NUTC_OS_PAGESIZE)
1393 	phpdbg_pagesize = sysconf(_SC_NUTC_OS_PAGESIZE);
1394 #else
1395 	phpdbg_pagesize = 4096; /* common pagesize */
1396 #endif
1397 
1398 	phpdbg_btree_init(&PHPDBG_G(watchpoint_tree), sizeof(void *) * 8);
1399 	phpdbg_btree_init(&PHPDBG_G(watch_HashTables), sizeof(void *) * 8);
1400 	zend_hash_init(&PHPDBG_G(watch_elements), 8, NULL, NULL, 0);
1401 	zend_hash_init(&PHPDBG_G(watch_collisions), 8, NULL, NULL, 0);
1402 	zend_hash_init(&PHPDBG_G(watch_recreation), 8, NULL, NULL, 0);
1403 	zend_hash_init(&PHPDBG_G(watch_free), 8, NULL, NULL, 0);
1404 
1405 	/* put these on a separate page, to avoid conflicts with other memory */
1406 	PHPDBG_G(watchlist_mem) = malloc(phpdbg_pagesize > sizeof(HashTable) ? phpdbg_pagesize : sizeof(HashTable));
1407 	zend_hash_init(PHPDBG_G(watchlist_mem), phpdbg_pagesize / (sizeof(Bucket) + sizeof(uint32_t)), NULL, NULL, 1);
1408 	PHPDBG_G(watchlist_mem_backup) = malloc(phpdbg_pagesize > sizeof(HashTable) ? phpdbg_pagesize : sizeof(HashTable));
1409 	zend_hash_init(PHPDBG_G(watchlist_mem_backup), phpdbg_pagesize / (sizeof(Bucket) + sizeof(uint32_t)), NULL, NULL, 1);
1410 
1411 	PHPDBG_G(watch_tmp) = NULL;
1412 }
1413 
1414 void phpdbg_destroy_watchpoints(void) {
1415 	phpdbg_watch_element *element;
1416 	phpdbg_btree_position pos;
1417 	phpdbg_btree_result *res;
1418 
1419 	/* unconditionally free all remaining elements to avoid memory leaks */
1420 	ZEND_HASH_FOREACH_PTR(&PHPDBG_G(watch_recreation), element) {
1421 		phpdbg_automatic_dequeue_free(element);
1422 	} ZEND_HASH_FOREACH_END();
1423 
1424 	/* 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. */
1425 	pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), 0, -1);
1426 	while ((res = phpdbg_btree_next(&pos))) {
1427 		phpdbg_deactivate_watchpoint(res->ptr);
1428 	}
1429 
1430 	zend_hash_destroy(&PHPDBG_G(watch_elements)); PHPDBG_G(watch_elements).nNumOfElements = 0; /* phpdbg_watch_efree() is checking against this arrays size */
1431 	zend_hash_destroy(&PHPDBG_G(watch_recreation));
1432 	zend_hash_destroy(&PHPDBG_G(watch_free));
1433 	zend_hash_destroy(&PHPDBG_G(watch_collisions));
1434 	zend_hash_destroy(PHPDBG_G(watchlist_mem));
1435 	free(PHPDBG_G(watchlist_mem));
1436 	zend_hash_destroy(PHPDBG_G(watchlist_mem_backup));
1437 	free(PHPDBG_G(watchlist_mem_backup));
1438 }
1439 
1440 void phpdbg_purge_watchpoint_tree(void) {
1441 	phpdbg_btree_position pos;
1442 	phpdbg_btree_result *res;
1443 
1444 	pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), 0, -1);
1445 	while ((res = phpdbg_btree_next(&pos))) {
1446 		phpdbg_deactivate_watchpoint(res->ptr);
1447 	}
1448 }
1449