xref: /PHP-7.4/sapi/phpdbg/phpdbg_watch.c (revision af4a9bf1)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 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 explicitly 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 implicitly 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 			GC_MAKE_PERSISTENT_LOCAL(watch->backup.str);
330 			break;
331 		case WATCH_ON_HASHTABLE:
332 			memcpy((char *) &watch->backup + HT_WATCH_OFFSET, watch->addr.ptr, watch->size);
333 		case WATCH_ON_HASHDATA:
334 			break;
335 	}
336 }
337 
338 /* ### MANAGE WATCH COLLISIONS ### To be used only by watch element manager and memory differ ### */
339 /* watch collisions are responsible for having only one watcher on a given refcounted/refval and having a mapping back to the parent zvals */
340 void phpdbg_delete_watch_collision(phpdbg_watchpoint_t *watch) {
341 	phpdbg_watch_collision *coll;
342 	if ((coll = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref))) {
343 		zend_hash_index_del(&coll->parents, (zend_ulong) watch);
344 		if (zend_hash_num_elements(&coll->parents) == 0) {
345 			phpdbg_deactivate_watchpoint(&coll->ref);
346 			phpdbg_remove_watchpoint_btree(&coll->ref);
347 
348 			if (coll->ref.type == WATCH_ON_ZVAL) {
349 				phpdbg_delete_watch_collision(&coll->ref);
350 			} else if (coll->reference.addr.ptr) {
351 				phpdbg_deactivate_watchpoint(&coll->reference);
352 				phpdbg_remove_watchpoint_btree(&coll->reference);
353 				phpdbg_delete_watch_collision(&coll->reference);
354 				if (coll->reference.type == WATCH_ON_STR) {
355 					zend_string_release(coll->reference.backup.str);
356 				}
357 			}
358 
359 			zend_hash_index_del(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref);
360 			zend_hash_destroy(&coll->parents);
361 			efree(coll);
362 		}
363 	}
364 }
365 
366 void phpdbg_update_watch_ref(phpdbg_watchpoint_t *watch) {
367 	phpdbg_watch_collision *coll;
368 
369 	ZEND_ASSERT(watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET);
370 	if (Z_REFCOUNTED_P(watch->addr.zv)) {
371 		if (Z_COUNTED_P(watch->addr.zv) == watch->ref) {
372 			return;
373 		}
374 
375 		if (watch->ref != NULL) {
376 			phpdbg_delete_watch_collision(watch);
377 		}
378 
379 		watch->ref = Z_COUNTED_P(watch->addr.zv);
380 
381 		if (!(coll = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref))) {
382 			coll = emalloc(sizeof(*coll));
383 			coll->ref.type = WATCH_ON_REFCOUNTED;
384 			phpdbg_set_addr_watchpoint(Z_COUNTED_P(watch->addr.zv), sizeof(uint32_t), &coll->ref);
385 			coll->ref.coll = coll;
386 			phpdbg_store_watchpoint_btree(&coll->ref);
387 			phpdbg_activate_watchpoint(&coll->ref);
388 			phpdbg_watch_backup_data(&coll->ref);
389 
390 			if (Z_ISREF_P(watch->addr.zv)) {
391 				phpdbg_set_zval_watchpoint(Z_REFVAL_P(watch->addr.zv), &coll->reference);
392 				coll->reference.coll = coll;
393 				phpdbg_update_watch_ref(&coll->reference);
394 				phpdbg_store_watchpoint_btree(&coll->reference);
395 				phpdbg_activate_watchpoint(&coll->reference);
396 				phpdbg_watch_backup_data(&coll->reference);
397 			} else if (Z_TYPE_P(watch->addr.zv) == IS_STRING) {
398 				coll->reference.type = WATCH_ON_STR;
399 				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);
400 				coll->reference.coll = coll;
401 				phpdbg_store_watchpoint_btree(&coll->reference);
402 				phpdbg_activate_watchpoint(&coll->reference);
403 				coll->reference.backup.str = NULL;
404 				phpdbg_watch_backup_data(&coll->reference);
405 			} else {
406 				coll->reference.addr.ptr = NULL;
407 			}
408 
409 			zend_hash_init(&coll->parents, 8, shitty stupid parameter, NULL, 0);
410 			zend_hash_index_add_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref, coll);
411 		}
412 		zend_hash_index_add_ptr(&coll->parents, (zend_long) watch, watch);
413 	} else if (Z_TYPE_P(watch->addr.zv) == IS_INDIRECT) {
414 		if ((zend_refcounted *) Z_INDIRECT_P(watch->addr.zv) == watch->ref) {
415 			return;
416 		}
417 
418 		if (watch->ref != NULL) {
419 			phpdbg_delete_watch_collision(watch);
420 		}
421 
422 		watch->ref = (zend_refcounted *) Z_INDIRECT_P(watch->addr.zv);
423 
424 		if (!(coll = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref))) {
425 			coll = emalloc(sizeof(*coll));
426 			phpdbg_set_zval_watchpoint(Z_INDIRECT_P(watch->addr.zv), &coll->ref);
427 			coll->ref.coll = coll;
428 			phpdbg_update_watch_ref(&coll->ref);
429 			phpdbg_store_watchpoint_btree(&coll->ref);
430 			phpdbg_activate_watchpoint(&coll->ref);
431 			phpdbg_watch_backup_data(&coll->ref);
432 
433 			zend_hash_init(&coll->parents, 8, shitty stupid parameter, NULL, 0);
434 			zend_hash_index_add_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref, coll);
435 		}
436 		zend_hash_index_add_ptr(&coll->parents, (zend_long) watch, watch);
437 	} else if (watch->ref) {
438 		phpdbg_delete_watch_collision(watch);
439 		watch->ref = NULL;
440 	}
441 }
442 
443 /* ### MANAGE WATCH ELEMENTS ### */
444 /* watchpoints must be unique per element. Only one watchpoint may point to one element. But many elements may point to one watchpoint. */
445 void phpdbg_recurse_watch_element(phpdbg_watch_element *element);
446 void phpdbg_remove_watch_element_recursively(phpdbg_watch_element *element);
447 void phpdbg_free_watch_element(phpdbg_watch_element *element);
448 void phpdbg_remove_watchpoint(phpdbg_watchpoint_t *watch);
449 void phpdbg_watch_parent_ht(phpdbg_watch_element *element);
450 
451 phpdbg_watch_element *phpdbg_add_watch_element(phpdbg_watchpoint_t *watch, phpdbg_watch_element *element) {
452 	phpdbg_btree_result *res;
453 	if ((res = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr)) == NULL) {
454 		phpdbg_watchpoint_t *mem = emalloc(sizeof(*mem));
455 		*mem = *watch;
456 		watch = mem;
457 		phpdbg_store_watchpoint_btree(watch);
458 		if (watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) {
459 			phpdbg_update_watch_ref(watch);
460 		}
461 		phpdbg_activate_watchpoint(watch);
462 		phpdbg_watch_backup_data(watch);
463 	} else {
464 		phpdbg_watch_element *old_element;
465 		watch = res->ptr;
466 		if ((old_element = zend_hash_find_ptr(&watch->elements, element->str))) {
467 			phpdbg_free_watch_element(element);
468 			return old_element;
469 		}
470 	}
471 
472 	element->watch = watch;
473 	zend_hash_add_ptr(&watch->elements, element->str, element);
474 
475 	if (element->flags & PHPDBG_WATCH_RECURSIVE) {
476 		phpdbg_recurse_watch_element(element);
477 	}
478 
479 	return element;
480 }
481 
482 phpdbg_watch_element *phpdbg_add_bucket_watch_element(Bucket *bucket, phpdbg_watch_element *element) {
483 	phpdbg_watchpoint_t watch;
484 	phpdbg_set_bucket_watchpoint(bucket, &watch);
485 	element = phpdbg_add_watch_element(&watch, element);
486 	phpdbg_watch_parent_ht(element);
487 	return element;
488 }
489 
490 phpdbg_watch_element *phpdbg_add_ht_watch_element(zval *zv, phpdbg_watch_element *element) {
491 	phpdbg_watchpoint_t watch;
492 	HashTable *ht = HT_FROM_ZVP(zv);
493 
494 	if (!ht) {
495 		return NULL;
496 	}
497 
498 	element->flags |= Z_TYPE_P(zv) == IS_ARRAY ? PHPDBG_WATCH_ARRAY : PHPDBG_WATCH_OBJECT;
499 	phpdbg_set_ht_watchpoint(ht, &watch);
500 	return phpdbg_add_watch_element(&watch, element);
501 }
502 
503 zend_bool phpdbg_is_recursively_watched(void *ptr, phpdbg_watch_element *element) {
504 	phpdbg_watch_element *next = element;
505 	do {
506 		element = next;
507 		if (element->watch->addr.ptr == ptr) {
508 			return 1;
509 		}
510 		next = element->parent;
511 	} while (!(element->flags & PHPDBG_WATCH_RECURSIVE_ROOT));
512 
513 	return 0;
514 }
515 
516 void phpdbg_add_recursive_watch_from_ht(phpdbg_watch_element *element, zend_long idx, zend_string *str, zval *zv) {
517 	phpdbg_watch_element *child;
518 	if (phpdbg_is_recursively_watched(zv, element)) {
519 		return;
520 	}
521 
522 	child = emalloc(sizeof(*child));
523 	child->flags = PHPDBG_WATCH_RECURSIVE;
524 	if (str) {
525 		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)));
526 	} else {
527 		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);
528 	}
529 	if (!str) {
530 		str = zend_long_to_str(idx); // TODO: hack, use proper int handling for name in parent
531 	} else { str = zend_string_copy(str); }
532 	child->name_in_parent = str;
533 	child->parent = element;
534 	child->child = NULL;
535 	child->parent_container = HT_WATCH_HT(element->watch);
536 	zend_hash_add_ptr(&element->child_container, child->str, child);
537 	phpdbg_add_bucket_watch_element((Bucket *) zv, child);
538 }
539 
540 void phpdbg_recurse_watch_element(phpdbg_watch_element *element) {
541 	phpdbg_watch_element *child;
542 	zval *zv;
543 
544 	if (element->watch->type == WATCH_ON_ZVAL || element->watch->type == WATCH_ON_BUCKET) {
545 		zv = element->watch->addr.zv;
546 		while (Z_TYPE_P(zv) == IS_INDIRECT) {
547 			zv = Z_INDIRECT_P(zv);
548 		}
549 		ZVAL_DEREF(zv);
550 
551 		if (element->child) {
552 			phpdbg_remove_watch_element_recursively(element->child);
553 		}
554 
555 		if ((Z_TYPE_P(zv) != IS_ARRAY && Z_TYPE_P(zv) != IS_OBJECT)
556 		    || phpdbg_is_recursively_watched(HT_WATCH_OFFSET + (char *) HT_FROM_ZVP(zv), element)) {
557 			if (element->child) {
558 				phpdbg_free_watch_element(element->child);
559 				element->child = NULL;
560 			}
561 			return;
562 		}
563 
564 		if (element->child) {
565 			child = element->child;
566 		} else {
567 			child = emalloc(sizeof(*child));
568 			child->flags = PHPDBG_WATCH_RECURSIVE;
569 			child->str = strpprintf(0, "%.*s[]", (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str));
570 			child->name_in_parent = NULL;
571 			child->parent = element;
572 			child->child = NULL;
573 			element->child = child;
574 		}
575 		zend_hash_init(&child->child_container, 8, NULL, NULL, 0);
576 		phpdbg_add_ht_watch_element(zv, child);
577 	} else if (zend_hash_num_elements(&element->child_container) == 0) {
578 		zend_string *str;
579 		zend_long idx;
580 
581 		ZEND_ASSERT(element->watch->type == WATCH_ON_HASHTABLE);
582 		ZEND_HASH_FOREACH_KEY_VAL(HT_WATCH_HT(element->watch), idx, str, zv) {
583 			phpdbg_add_recursive_watch_from_ht(element, idx, str, zv);
584 		} ZEND_HASH_FOREACH_END();
585 	}
586 }
587 
588 void phpdbg_watch_parent_ht(phpdbg_watch_element *element) {
589 	if (element->watch->type == WATCH_ON_BUCKET) {
590 		phpdbg_btree_result *res;
591 		HashPosition pos;
592 		phpdbg_watch_ht_info *hti;
593 		ZEND_ASSERT(element->parent_container);
594 		if (!(res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) element->parent_container))) {
595 			hti = emalloc(sizeof(*hti));
596 			hti->ht = element->parent_container;
597 
598 			zend_hash_init(&hti->watches, 0, grrrrr, ZVAL_PTR_DTOR, 0);
599 			phpdbg_btree_insert(&PHPDBG_G(watch_HashTables), (zend_ulong) hti->ht, hti);
600 
601 			phpdbg_set_addr_watchpoint(HT_GET_DATA_ADDR(hti->ht), HT_HASH_SIZE(hti->ht->nTableMask), &hti->hash_watch);
602 			hti->hash_watch.type = WATCH_ON_HASHDATA;
603 			phpdbg_store_watchpoint_btree(&hti->hash_watch);
604 			phpdbg_activate_watchpoint(&hti->hash_watch);
605 		} else {
606 			hti = (phpdbg_watch_ht_info *) res->ptr;
607 		}
608 
609 		zend_hash_internal_pointer_end_ex(hti->ht, &pos);
610 		hti->last = hti->ht->arData + pos;
611 		hti->last_str = hti->last->key;
612 		hti->last_idx = hti->last->h;
613 
614 		zend_hash_add_ptr(&hti->watches, element->name_in_parent, element);
615 	}
616 }
617 
618 void phpdbg_unwatch_parent_ht(phpdbg_watch_element *element) {
619 	if (element->watch->type == WATCH_ON_BUCKET) {
620 		phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) element->parent_container);
621 		ZEND_ASSERT(element->parent_container);
622 		if (res) {
623 			phpdbg_watch_ht_info *hti = res->ptr;
624 
625 			if (zend_hash_num_elements(&hti->watches) == 1) {
626 				zend_hash_destroy(&hti->watches);
627 				phpdbg_btree_delete(&PHPDBG_G(watch_HashTables), (zend_ulong) hti->ht);
628 				phpdbg_deactivate_watchpoint(&hti->hash_watch);
629 				phpdbg_remove_watchpoint_btree(&hti->hash_watch);
630 				efree(hti);
631 			} else {
632 				zend_hash_del(&hti->watches, element->name_in_parent);
633 			}
634 		}
635 	}
636 }
637 
638 /* ### DE/QUEUE WATCH ELEMENTS ### to be used by watch element manager only */
639 /* 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 */
640 
641 void phpdbg_dissociate_watch_element(phpdbg_watch_element *element, phpdbg_watch_element *until);
642 void phpdbg_free_watch_element_tree(phpdbg_watch_element *element);
643 
644 void phpdbg_queue_element_for_recreation(phpdbg_watch_element *element) {
645 	/* store lowermost element */
646 	phpdbg_watch_element *prev;
647 
648 	if ((prev = zend_hash_find_ptr(&PHPDBG_G(watch_recreation), element->str))) {
649 		phpdbg_watch_element *child = prev;
650 		do {
651 			if (child == element) {
652 				return;
653 			}
654 			child = child->child;
655 		} while (child);
656 	}
657 	zend_hash_update_ptr(&PHPDBG_G(watch_recreation), element->str, element);
658 
659 	/* dissociate from watchpoint to avoid dangling memory watches */
660 	phpdbg_dissociate_watch_element(element, prev);
661 
662 	if (!element->parent) {
663 		/* 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) */
664 		zend_hash_index_add_empty_element(&PHPDBG_G(watch_free), (zend_ulong) element->parent_container);
665 	}
666 }
667 
668 zend_bool phpdbg_try_readding_watch_element(zval *parent, phpdbg_watch_element *element) {
669 	zval *zv;
670 	HashTable *ht = HT_FROM_ZVP(parent);
671 
672 	if (!ht) {
673 		return 0;
674 	} else if (element->flags & (PHPDBG_WATCH_ARRAY | PHPDBG_WATCH_OBJECT)) {
675 		char *htPtr = ((char *) ht) + HT_WATCH_OFFSET;
676 		char *oldPtr = ((char *) &element->backup.ht) + HT_WATCH_OFFSET;
677 		if (phpdbg_check_watch_diff(WATCH_ON_HASHTABLE, oldPtr, htPtr)) {
678 			phpdbg_print_watch_diff(WATCH_ON_HASHTABLE, element->str, oldPtr, htPtr);
679 		}
680 
681 		phpdbg_add_ht_watch_element(parent, element);
682 	} else if ((zv = zend_symtable_find(ht, element->name_in_parent))) {
683 		if (element->flags & PHPDBG_WATCH_IMPLICIT) {
684 			zval *next = zv;
685 
686 			while (Z_TYPE_P(next) == IS_INDIRECT) {
687 				next = Z_INDIRECT_P(next);
688 			}
689 			if (Z_ISREF_P(next)) {
690 				next = Z_REFVAL_P(next);
691 			}
692 
693 			if (!phpdbg_try_readding_watch_element(next, element->child)) {
694 				return 0;
695 			}
696 		} else if (phpdbg_check_watch_diff(WATCH_ON_ZVAL, &element->backup.zv, zv)) {
697 			phpdbg_print_watch_diff(WATCH_ON_ZVAL, element->str, &element->backup.zv, zv);
698 		}
699 
700 		element->parent_container = ht;
701 		phpdbg_add_bucket_watch_element((Bucket *) zv, element);
702 		phpdbg_watch_parent_ht(element);
703 	} else {
704 		return 0;
705 	}
706 
707 	return 1;
708 }
709 
710 void phpdbg_automatic_dequeue_free(phpdbg_watch_element *element) {
711 	phpdbg_watch_element *child = element;
712 	while (child->child && !(child->flags & PHPDBG_WATCH_RECURSIVE_ROOT)) {
713 		child = child->child;
714 	}
715 	PHPDBG_G(watchpoint_hit) = 1;
716 	if (zend_hash_index_del(&PHPDBG_G(watch_elements), child->id) == SUCCESS) {
717 		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" : "");
718 	}
719 	phpdbg_free_watch_element_tree(element);
720 }
721 
722 void phpdbg_dequeue_elements_for_recreation() {
723 	phpdbg_watch_element *element;
724 
725 	ZEND_HASH_FOREACH_PTR(&PHPDBG_G(watch_recreation), element) {
726 		ZEND_ASSERT(element->flags & (PHPDBG_WATCH_IMPLICIT | PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_SIMPLE));
727 		if (element->parent || zend_hash_index_find(&PHPDBG_G(watch_free), (zend_ulong) element->parent_container)) {
728 			zval _zv, *zv = &_zv;
729 			if (element->parent) {
730 				ZEND_ASSERT(element->parent->watch->type == WATCH_ON_ZVAL || element->parent->watch->type == WATCH_ON_BUCKET);
731 				zv = element->parent->watch->addr.zv;
732 				while (Z_TYPE_P(zv) == IS_INDIRECT) {
733 					zv = Z_INDIRECT_P(zv);
734 				}
735 				ZVAL_DEREF(zv);
736 			} else {
737 				ZVAL_ARR(zv, element->parent_container);
738 			}
739 			if (!phpdbg_try_readding_watch_element(zv, element)) {
740 				phpdbg_automatic_dequeue_free(element);
741 			}
742 		} else {
743 			phpdbg_automatic_dequeue_free(element);
744 		}
745 	} ZEND_HASH_FOREACH_END();
746 
747 	zend_hash_clean(&PHPDBG_G(watch_recreation));
748 	zend_hash_clean(&PHPDBG_G(watch_free));
749 }
750 
751 /* ### WATCH ELEMENT DELETION ### only use phpdbg_remove_watch_element from the exterior */
752 void phpdbg_clean_watch_element(phpdbg_watch_element *element);
753 
754 void phpdbg_free_watch_element(phpdbg_watch_element *element) {
755 	zend_string_release(element->str);
756 	if (element->name_in_parent) {
757 		zend_string_release(element->name_in_parent);
758 	}
759 	efree(element);
760 }
761 
762 /* note: does *not* free the passed element, only clean */
763 void phpdbg_remove_watch_element_recursively(phpdbg_watch_element *element) {
764 	if (element->child) {
765 		phpdbg_remove_watch_element_recursively(element->child);
766 		phpdbg_free_watch_element(element->child);
767 		element->child = NULL;
768 	} else if (element->flags & (PHPDBG_WATCH_ARRAY | PHPDBG_WATCH_OBJECT)) {
769 		phpdbg_watch_element *child;
770 		ZEND_HASH_FOREACH_PTR(&element->child_container, child) {
771 			phpdbg_remove_watch_element_recursively(child);
772 			phpdbg_free_watch_element(child);
773 		} ZEND_HASH_FOREACH_END();
774 		zend_hash_destroy(&element->child_container);
775 	}
776 
777 	phpdbg_clean_watch_element(element);
778 }
779 
780 /* remove single watch (i.e. manual unset) or implicit removed */
781 void phpdbg_remove_watch_element(phpdbg_watch_element *element) {
782 	phpdbg_watch_element *parent = element->parent, *child = element->child;
783 	while (parent) {
784 		phpdbg_watch_element *cur = parent;
785 		parent = parent->parent;
786 		phpdbg_clean_watch_element(cur);
787 		phpdbg_free_watch_element(cur);
788 	}
789 	while (child) {
790 		phpdbg_watch_element *cur = child;
791 		child = child->child;
792 		if (cur->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
793 			phpdbg_remove_watch_element_recursively(cur);
794 			child = NULL;
795 		} else {
796 			phpdbg_clean_watch_element(cur);
797 		}
798 		phpdbg_free_watch_element(cur);
799 	}
800 	if (element->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
801 		phpdbg_remove_watch_element_recursively(element);
802 	} else {
803 		phpdbg_clean_watch_element(element);
804 	}
805 	zend_hash_index_del(&PHPDBG_G(watch_elements), element->id);
806 	phpdbg_free_watch_element(element);
807 }
808 
809 void phpdbg_backup_watch_element(phpdbg_watch_element *element) {
810 	memcpy(&element->backup, &element->watch->backup, /* element->watch->size */ sizeof(element->backup));
811 }
812 
813 /* until argument to prevent double remove of children elements */
814 void phpdbg_dissociate_watch_element(phpdbg_watch_element *element, phpdbg_watch_element *until) {
815 	phpdbg_watch_element *child = element;
816 	ZEND_ASSERT((element->flags & (PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_RECURSIVE)) != PHPDBG_WATCH_RECURSIVE);
817 
818 	if (element->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
819 		phpdbg_backup_watch_element(element);
820 		phpdbg_remove_watch_element_recursively(element);
821 		return;
822 	}
823 
824 	while (child->child != until) {
825 		child = child->child;
826 		if (child->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
827 			phpdbg_backup_watch_element(child);
828 			phpdbg_remove_watch_element_recursively(child);
829 			child->child = NULL;
830 			break;
831 		}
832 		if (child->child == NULL || (child->flags & PHPDBG_WATCH_RECURSIVE_ROOT)) {
833 			phpdbg_backup_watch_element(child);
834 		}
835 		phpdbg_clean_watch_element(child);
836 	}
837 	/* element needs to be removed last! */
838 	if (element->child == NULL) {
839 		phpdbg_backup_watch_element(element);
840 	}
841 	phpdbg_clean_watch_element(element);
842 }
843 
844 /* 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) */
845 void phpdbg_free_watch_element_tree(phpdbg_watch_element *element) {
846 	phpdbg_watch_element *parent = element->parent, *child = element->child;
847 	while (parent) {
848 		phpdbg_watch_element *cur = parent;
849 		parent = parent->parent;
850 		phpdbg_clean_watch_element(cur);
851 		phpdbg_free_watch_element(cur);
852 	}
853 	while (child) {
854 		phpdbg_watch_element *cur = child;
855 		child = child->child;
856 		phpdbg_free_watch_element(cur);
857 	}
858 	phpdbg_free_watch_element(element);
859 }
860 
861 void phpdbg_update_watch_element_watch(phpdbg_watch_element *element) {
862 	if (element->flags & PHPDBG_WATCH_IMPLICIT) {
863 		phpdbg_watch_element *child = element->child;
864 		while (child->flags & PHPDBG_WATCH_IMPLICIT) {
865 			child = child->child;
866 		}
867 
868 		ZEND_ASSERT(element->watch->type == WATCH_ON_ZVAL || element->watch->type == WATCH_ON_BUCKET);
869 		phpdbg_queue_element_for_recreation(element);
870 	} else if (element->flags & (PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_SIMPLE)) {
871 		phpdbg_queue_element_for_recreation(element);
872 	} else if (element->flags & PHPDBG_WATCH_RECURSIVE) {
873 		phpdbg_remove_watch_element_recursively(element);
874 		if (element->parent->flags & (PHPDBG_WATCH_OBJECT | PHPDBG_WATCH_ARRAY)) {
875 			zend_hash_del(&element->parent->child_container, element->str);
876 		} else {
877 			element->parent->child = NULL;
878 		}
879 		phpdbg_free_watch_element(element);
880 	}
881 }
882 
883 void phpdbg_update_watch_collision_elements(phpdbg_watchpoint_t *watch) {
884 	phpdbg_watchpoint_t *parent;
885 	phpdbg_watch_element *element;
886 
887 	ZEND_HASH_FOREACH_PTR(&watch->coll->parents, parent) {
888 		if (parent->coll) {
889 			phpdbg_update_watch_collision_elements(parent);
890 		} else {
891 			ZEND_HASH_FOREACH_PTR(&parent->elements, element) {
892 				phpdbg_update_watch_element_watch(element);
893 			} ZEND_HASH_FOREACH_END();
894 		}
895 	} ZEND_HASH_FOREACH_END();
896 }
897 
898 void phpdbg_remove_watchpoint(phpdbg_watchpoint_t *watch) {
899 	phpdbg_watch_element *element;
900 
901 	phpdbg_deactivate_watchpoint(watch);
902 	phpdbg_remove_watchpoint_btree(watch);
903 	phpdbg_delete_watch_collision(watch);
904 
905 	if (watch->coll) {
906 		phpdbg_update_watch_collision_elements(watch);
907 		return;
908 	}
909 
910 	watch->elements.nNumOfElements++; /* dirty hack to avoid double free */
911 	ZEND_HASH_FOREACH_PTR(&watch->elements, element) {
912 		phpdbg_update_watch_element_watch(element);
913 	} ZEND_HASH_FOREACH_END();
914 	zend_hash_destroy(&watch->elements);
915 
916 	efree(watch);
917 }
918 
919 void phpdbg_clean_watch_element(phpdbg_watch_element *element) {
920 	HashTable *elements = &element->watch->elements;
921 	phpdbg_unwatch_parent_ht(element);
922 	zend_hash_del(elements, element->str);
923 	if (zend_hash_num_elements(elements) == 0) {
924 		phpdbg_remove_watchpoint(element->watch);
925 	}
926 }
927 
928 /* TODO: compile a name of all hit watchpoints (ids ??) */
929 zend_string *phpdbg_watchpoint_change_collision_name(phpdbg_watchpoint_t *watch) {
930 	phpdbg_watchpoint_t *parent;
931 	phpdbg_watch_element *element;
932 	zend_string *name = NULL;
933 	if (watch->coll) {
934 		ZEND_HASH_FOREACH_PTR(&watch->coll->parents, parent) {
935 			if (name) {
936 				zend_string_release(name);
937 			}
938 			name = phpdbg_watchpoint_change_collision_name(parent);
939 		} ZEND_HASH_FOREACH_END();
940 		return name;
941 	}
942 	ZEND_HASH_FOREACH_PTR(&watch->elements, element) {
943 		if (element->flags & PHPDBG_WATCH_IMPLICIT) {
944 			if ((watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) && Z_TYPE(watch->backup.zv) > IS_STRING) {
945 				phpdbg_update_watch_element_watch(element->child);
946 			}
947 			continue;
948 		}
949 		name = element->str;
950 	} ZEND_HASH_FOREACH_END();
951 
952 	return name ? zend_string_copy(name) : NULL;
953 }
954 
955 /* ### WATCHING FOR CHANGES ### */
956 /* TODO: enforce order: first parents, then children, in order to avoid false positives */
957 void phpdbg_check_watchpoint(phpdbg_watchpoint_t *watch) {
958 	zend_string *name = NULL;
959 	void *comparePtr;
960 
961 	if (watch->type == WATCH_ON_HASHTABLE) {
962 		phpdbg_watch_element *element;
963 		zend_string *str;
964 		zend_long idx;
965 		zval *zv;
966 		ZEND_HASH_FOREACH_PTR(&watch->elements, element) {
967 			if (element->flags & PHPDBG_WATCH_RECURSIVE) {
968 				phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) HT_WATCH_HT(watch));
969 				phpdbg_watch_ht_info *hti = res ? res->ptr : NULL;
970 
971 				ZEND_HASH_REVERSE_FOREACH_KEY_VAL(HT_WATCH_HT(watch), idx, str, zv) {
972 					if (!str) {
973 						str = zend_long_to_str(idx); // TODO: hack, use proper int handling for name in parent
974 					} else {
975 						str = zend_string_copy(str);
976 					}
977 					if (hti && zend_hash_find(&hti->watches, str)) {
978 						zend_string_release(str);
979 						break;
980 					}
981 					ZEND_HASH_FOREACH_PTR(&watch->elements, element) {
982 						if (element->flags & PHPDBG_WATCH_RECURSIVE) {
983 							phpdbg_add_recursive_watch_from_ht(element, idx, str, zv);
984 						}
985 					} ZEND_HASH_FOREACH_END();
986 					phpdbg_notice("watchadd", "element=\"%.*s\"", "Element %.*s has been added to watchpoint", (int) ZSTR_LEN(str), ZSTR_VAL(str));
987 					zend_string_release(str);
988 					PHPDBG_G(watchpoint_hit) = 1;
989 				} ZEND_HASH_FOREACH_END();
990 
991 				break;
992 			}
993 		} ZEND_HASH_FOREACH_END();
994 	}
995 	if (watch->type == WATCH_ON_HASHDATA) {
996 		return;
997 	}
998 
999 	switch (watch->type) {
1000 		case WATCH_ON_STR:
1001 			comparePtr = &ZSTR_LEN(watch->backup.str);
1002 			break;
1003 		case WATCH_ON_HASHTABLE:
1004 			comparePtr = (char *) &watch->backup.ht + HT_WATCH_OFFSET;
1005 			break;
1006 		default:
1007 			comparePtr = &watch->backup;
1008 	}
1009 	if (!phpdbg_check_watch_diff(watch->type, comparePtr, watch->addr.ptr)) {
1010 		return;
1011 	}
1012 	if (watch->type == WATCH_ON_REFCOUNTED && !(PHPDBG_G(flags) & PHPDBG_SHOW_REFCOUNTS)) {
1013 		phpdbg_watch_backup_data(watch);
1014 		return;
1015 	}
1016 	if (watch->type == WATCH_ON_BUCKET) {
1017 		if (watch->backup.bucket.key != watch->addr.bucket->key || (watch->backup.bucket.key != NULL && watch->backup.bucket.h != watch->addr.bucket->h)) {
1018 			phpdbg_watch_element *element = NULL;
1019 			zval *new;
1020 
1021 			ZEND_HASH_FOREACH_PTR(&watch->elements, element) {
1022 				break;
1023 			} ZEND_HASH_FOREACH_END();
1024 
1025 			ZEND_ASSERT(element); /* elements must be non-empty */
1026 			new = zend_symtable_find(element->parent_container, element->name_in_parent);
1027 
1028 			if (!new) {
1029 				/* dequeuing will take care of appropriate notification about removal */
1030 				phpdbg_remove_watchpoint(watch);
1031 				return;
1032 			}
1033 
1034 			phpdbg_deactivate_watchpoint(watch);
1035 			phpdbg_remove_watchpoint_btree(watch);
1036 			watch->addr.zv = new;
1037 			phpdbg_store_watchpoint_btree(watch);
1038 			phpdbg_activate_watchpoint(watch);
1039 
1040 			if (!phpdbg_check_watch_diff(WATCH_ON_ZVAL, &watch->backup.bucket.val, watch->addr.ptr)) {
1041 				phpdbg_watch_backup_data(watch);
1042 				return;
1043 			}
1044 		} else if (Z_TYPE_P(watch->addr.zv) == IS_UNDEF) {
1045 			/* dequeuing will take care of appropriate notification about removal */
1046 			phpdbg_remove_watchpoint(watch);
1047 			return;
1048 		}
1049 	}
1050 
1051 	name = phpdbg_watchpoint_change_collision_name(watch);
1052 
1053 	if (name) {
1054 		phpdbg_print_watch_diff(watch->type, name, comparePtr, watch->addr.ptr);
1055 		zend_string_release(name);
1056 	}
1057 
1058 	if (watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) {
1059 		phpdbg_watch_element *element;
1060 		phpdbg_update_watch_ref(watch);
1061 		ZEND_HASH_FOREACH_PTR(&watch->elements, element) {
1062 			if (element->flags & PHPDBG_WATCH_RECURSIVE) {
1063 				phpdbg_recurse_watch_element(element);
1064 			}
1065 		} ZEND_HASH_FOREACH_END();
1066 	}
1067 
1068 	phpdbg_watch_backup_data(watch);
1069 }
1070 
1071 void phpdbg_reenable_memory_watches(void) {
1072 	zend_ulong page;
1073 	phpdbg_btree_result *res;
1074 	phpdbg_watchpoint_t *watch;
1075 
1076 	ZEND_HASH_FOREACH_NUM_KEY(PHPDBG_G(watchlist_mem), page) {
1077 		/* Disble writing again if there are any watchers on that page */
1078 		res = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), page + phpdbg_pagesize - 1);
1079 		if (res) {
1080 			watch = res->ptr;
1081 			if ((char *) page < (char *) watch->addr.ptr + watch->size) {
1082 				mprotect((void *) page, phpdbg_pagesize, PROT_READ);
1083 			}
1084 		}
1085 	} ZEND_HASH_FOREACH_END();
1086 	zend_hash_clean(PHPDBG_G(watchlist_mem));
1087 }
1088 
1089 int phpdbg_print_changed_zvals(void) {
1090 	int ret;
1091 	zend_ulong page;
1092 	phpdbg_watchpoint_t *watch;
1093 	phpdbg_btree_result *res;
1094 	HashTable *mem_list = NULL;
1095 
1096 	if (zend_hash_num_elements(&PHPDBG_G(watch_elements)) == 0) {
1097 		return FAILURE;
1098 	}
1099 
1100 	if (zend_hash_num_elements(PHPDBG_G(watchlist_mem)) > 0) {
1101 		/* we must not add elements to the hashtable while iterating over it (resize => read into freed memory) */
1102 		mem_list = PHPDBG_G(watchlist_mem);
1103 		PHPDBG_G(watchlist_mem) = PHPDBG_G(watchlist_mem_backup);
1104 
1105 		ZEND_HASH_FOREACH_NUM_KEY(mem_list, page) {
1106 			phpdbg_btree_position pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), page, page + phpdbg_pagesize);
1107 
1108 			while ((res = phpdbg_btree_next(&pos))) {
1109 				watch = res->ptr;
1110 				phpdbg_check_watchpoint(watch);
1111 			}
1112 			if ((res = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), page - 1))) {
1113 				watch = res->ptr;
1114 				if ((char *) page < (char *) watch->addr.ptr + watch->size) {
1115 					phpdbg_check_watchpoint(watch);
1116 				}
1117 			}
1118 		} ZEND_HASH_FOREACH_END();
1119 	}
1120 
1121 	phpdbg_dequeue_elements_for_recreation();
1122 
1123 	phpdbg_reenable_memory_watches();
1124 
1125 	if (mem_list) {
1126 		PHPDBG_G(watchlist_mem) = mem_list;
1127 		phpdbg_reenable_memory_watches();
1128 	}
1129 
1130 	ret = PHPDBG_G(watchpoint_hit) ? SUCCESS : FAILURE;
1131 	PHPDBG_G(watchpoint_hit) = 0;
1132 
1133 	return ret;
1134 }
1135 
1136 void phpdbg_watch_efree(void *ptr) {
1137 	phpdbg_btree_result *result;
1138 
1139 	/* only do expensive checks if there are any watches at all */
1140 	if (zend_hash_num_elements(&PHPDBG_G(watch_elements))) {
1141 		if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) ptr))) {
1142 			phpdbg_watchpoint_t *watch = result->ptr;
1143 			if (watch->type != WATCH_ON_HASHDATA) {
1144 				phpdbg_remove_watchpoint(watch);
1145 			} else {
1146 				/* remove all linked watchpoints, they will be dissociated from their elements */
1147 				phpdbg_watch_element *element;
1148 				phpdbg_watch_ht_info *hti = (phpdbg_watch_ht_info *) watch;
1149 
1150 				ZEND_HASH_FOREACH_PTR(&hti->watches, element) {
1151 					zend_ulong num = zend_hash_num_elements(&hti->watches);
1152 					phpdbg_remove_watchpoint(element->watch);
1153 					if (num == 1) { /* prevent access into freed memory */
1154 						break;
1155 					}
1156 				} ZEND_HASH_FOREACH_END();
1157 			}
1158 		}
1159 
1160 		/* special case watchpoints as they aren't on ptr but on ptr + HT_WATCH_OFFSET */
1161 		if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), HT_WATCH_OFFSET + (zend_ulong) ptr))) {
1162 			phpdbg_watchpoint_t *watch = result->ptr;
1163 			if (watch->type == WATCH_ON_HASHTABLE) {
1164 				phpdbg_remove_watchpoint(watch);
1165 			}
1166 		}
1167 
1168 		zend_hash_index_del(&PHPDBG_G(watch_free), (zend_ulong) ptr);
1169 	}
1170 
1171 	if (PHPDBG_G(original_free_function)) {
1172 		PHPDBG_G(original_free_function)(ptr);
1173 	}
1174 }
1175 
1176 /* ### USER API ### */
1177 void phpdbg_list_watchpoints(void) {
1178 	phpdbg_watch_element *element;
1179 
1180 	phpdbg_xml("<watchlist %r>");
1181 
1182 	ZEND_HASH_FOREACH_PTR(&PHPDBG_G(watch_elements), element) {
1183 		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");
1184 	} ZEND_HASH_FOREACH_END();
1185 
1186 	phpdbg_xml("</watchlist>");
1187 }
1188 
1189 static int phpdbg_create_simple_watchpoint(zval *zv, phpdbg_watch_element *element) {
1190 	element->flags = PHPDBG_WATCH_SIMPLE;
1191 	phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1192 	return SUCCESS;
1193 }
1194 
1195 static int phpdbg_create_array_watchpoint(zval *zv, phpdbg_watch_element *element) {
1196 	phpdbg_watch_element *new;
1197 	zend_string *str;
1198 	zval *orig_zv = zv;
1199 
1200 	ZVAL_DEREF(zv);
1201 	if (Z_TYPE_P(zv) != IS_ARRAY && Z_TYPE_P(zv) != IS_OBJECT) {
1202 		return FAILURE;
1203 	}
1204 
1205 	new = ecalloc(1, sizeof(phpdbg_watch_element));
1206 
1207 	str = strpprintf(0, "%.*s[]", (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str));
1208 	zend_string_release(element->str);
1209 	element->str = str;
1210 	element->flags = PHPDBG_WATCH_IMPLICIT;
1211 	phpdbg_add_bucket_watch_element((Bucket *) orig_zv, element);
1212 	element->child = new;
1213 
1214 	new->flags = PHPDBG_WATCH_SIMPLE;
1215 	new->str = zend_string_copy(str);
1216 	new->parent = element;
1217 	phpdbg_add_ht_watch_element(zv, new);
1218 	return SUCCESS;
1219 }
1220 
1221 static int phpdbg_create_recursive_watchpoint(zval *zv, phpdbg_watch_element *element) {
1222 	element->flags = PHPDBG_WATCH_RECURSIVE | PHPDBG_WATCH_RECURSIVE_ROOT;
1223 	element->child = NULL;
1224 	phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1225 	return SUCCESS;
1226 }
1227 
1228 typedef struct { int (*callback)(zval *zv, phpdbg_watch_element *); zend_string *str; } phpdbg_watch_parse_struct;
1229 
1230 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) {
1231 	int ret;
1232 	phpdbg_watch_element *element = ecalloc(1, sizeof(phpdbg_watch_element));
1233 	element->str = zend_string_init(name, namelen, 0);
1234 	element->name_in_parent = zend_string_init(key, keylen, 0);
1235 	element->parent_container = parent;
1236 	element->parent = PHPDBG_G(watch_tmp);
1237 	element->child = NULL;
1238 
1239 	ret = info->callback(zv, element);
1240 
1241 	efree(name);
1242 	efree(key);
1243 
1244 	if (ret != SUCCESS) {
1245 		phpdbg_remove_watch_element(element);
1246 	} else {
1247 		if (PHPDBG_G(watch_tmp)) {
1248 			PHPDBG_G(watch_tmp)->child = element;
1249 		}
1250 
1251 		if (element->child) {
1252 			element = element->child;
1253 		}
1254 		element->id = PHPDBG_G(watch_elements).nNextFreeElement;
1255 		zend_hash_index_add_ptr(&PHPDBG_G(watch_elements), element->id, element);
1256 
1257 		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));
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 _SC_PAGE_SIZE
1389 	phpdbg_pagesize = sysconf(_SC_PAGE_SIZE);
1390 #elif _SC_PAGESIZE
1391 	phpdbg_pagesize = sysconf(_SC_PAGESIZE);
1392 #elif _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