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